You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
5.7 KiB
Markdown
91 lines
5.7 KiB
Markdown
# Web API
|
|
|
|
## Description
|
|
|
|
The Web API provides a controlled access to the evaluation backend. It also
|
|
enables the use of different user frontends such as default web application,
|
|
mobile applications, commandline tools and possibly others. The communication
|
|
goes as HTTP(S) requests in predefined format, nowadays mostly known as
|
|
[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) format.
|
|
Results from the API are in plain text in JSON format to be easily parsed in
|
|
various languages (notably JavaScript).
|
|
|
|
This component must be publicly visible on the internet, so it is important to
|
|
care about security and follow our recommendations. Security and user access
|
|
restriction are among our primary concerns, so proper roles with permission
|
|
separation are introduced and maintained. Also some additional checks are made
|
|
directly in the code, so that a user cannot access information which are out of
|
|
their authorization.
|
|
|
|
## Architecture
|
|
|
|
Web API is written in PHP using [Nette framework](https://nette.org/en/). This framework provides useful components like _Tracy_ for logging and showing errors, _Tester_ for productive unit testing or _Latte_ templating engine. Nette is modern, widely used and great performing software with active developers and user community. Nette can help eliminate security holes, simplify debugging and make coding easier with numerous plugins and extensions. Also, it is published under permissive BSD license.
|
|
|
|
API architecture consists of several parts:
|
|
|
|
- **router** -- component handling mapping from URL addresses to methods in presenter classes (called endpoints)
|
|
- **presenters** -- classes containing one method per endpoint responsible for
|
|
fetching and parsing request arguments and performing desired actions
|
|
- **entities** -- classes persisted using a database with an ORM framework
|
|
- **repositories** -- common operations on entities of one type, mostly finding entity by identifier or persisting changes to the database
|
|
- **helpers** -- set of classes solving more complicated internal logic, used from presenters to keep them reasonably small
|
|
|
|
Each presenter method has several annotations. They are used for generating REST API documentation in [Swagger](http://swagger.io/), specifying request type and its parameters and specifying one level of access restrictions. Also, there is simple description of the endpoint.
|
|
|
|
For specifying the request type (_GET_, _POST_, _DELETE_) annotations with exactly these names without any parameters are used. To describe request parameters `@Param` annotation is used with following arguments:
|
|
|
|
- type -- the type of argument, one of _post_ of _query_
|
|
- name -- name of the argument (the key)
|
|
- validation -- validation of the value, see [Nette validation rules](https://doc.nette.org/en/2.4/validators#toc-rules)
|
|
- msg -- description for users about the values this parameter can contain
|
|
- required -- specifies if this option is mandatory (`true`, default) or optional (`false`)
|
|
- description -- description for documentation of the API
|
|
|
|
Another annotation is `@LoggedIn` which takes no arguments. It can be placed before a whole class or before a method, so requests from unauthorized users are forbidden. Permissions can be granted or prohibitted by `@UserIsAllowed` annotation. This one is only per method and takes one argument in `key="value"` format. The value specifies which action (_value_) of a resource (_key_) the user needs to be allowed to perform this request. An example of how an annotated endpoint can look like:
|
|
|
|
```{.php}
|
|
/**
|
|
* Create a user account
|
|
* @POST
|
|
* @LoggedIn
|
|
* @UserIsAllowed(users="create")
|
|
* @Param(type="post", name="email", validation="email",
|
|
* description="An email that will serve as a login name")
|
|
* @Param(type="post", name="name", validation="string:2..",
|
|
* description="First name")
|
|
* @Param(type="post", name="password", validation="string:1..",
|
|
* msg="Password cannot be empty.",
|
|
* description="A password for authentication")
|
|
*/
|
|
public function actionCreateAccount() {
|
|
...
|
|
}
|
|
```
|
|
|
|
|
|
The [Doctrine](https://github.com/Kdyby/Doctrine)
|
|
[ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) framework is used
|
|
as an object persistence layer. It provides simple to use annotations to specify
|
|
columns of database tables including types, indexes and also it is possible to
|
|
make mapping between entities. For detailed info refer to [official
|
|
documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/).
|
|
|
|
The API is capable of sending email messages. They can inform an administrator about errors and users about submission evaluation or a temporary link to change forgotten password. The [Nette Mail](https://doc.nette.org/en/2.4/mailing) extension provides nice interface for sending messages through external SMTP server (**preferred**) or builtin PHP function `mail`. It is important to set up the mailserver properly to ensure message delivery to the clients. The messages are rendered in HTML format via simple _Latte_ templates.
|
|
|
|
### Authentication
|
|
|
|
Instead of relying on PHP sessions, we decided to use an authentication flow
|
|
based on JWT tokens (RFC 7519). On successful login, the user is issued an
|
|
access token that they have to send with subsequent requests using the HTTP
|
|
Authorization header (`Authorization: Bearer <token>`). The token has a limited
|
|
validity period and has to be renewed periodically using a dedicated API
|
|
endpoint.
|
|
|
|
To implement this behavior in Nette framework, a new `IUserStorage`
|
|
implementation was created, along with an `IIdentity` and authenticators for
|
|
both our internal login service and CAS.
|
|
|
|
An advantage of this approach is being able control the authentication process
|
|
completely instead of just receiving session data through a global variable.
|
|
|