Compare commits

...

4 Commits

Author SHA1 Message Date
Mal 44aaff929c OpenAPI documentation added 2020-08-21 23:47:13 +02:00
Mal a42f728ccf Fixes and improvements for session handling 2020-08-21 23:16:24 +02:00
Mal f4fdf50288 Fixes for controllers 2020-08-21 22:47:49 +02:00
Mal 3a99e99130 Error handling simplified 2020-08-21 22:46:19 +02:00
10 changed files with 534 additions and 35 deletions

475
api/v1/openapi.yaml Normal file
View File

@ -0,0 +1,475 @@
openapi: 3.0.0
info:
title: Ringfinger API
version: 1.0.0
description: The restfull API of the jabber fingerprint keyring.
servers:
- url: https://sabolli.de/ringfinger
description: Test server
tags:
- name: User
description: A user that can login, store fingerprints and share them with other users.
- name: Fingerprint
description: The jabber fingerprint owned by a user.
- name: Sharing
description: The sharing of all fingerprints of a user with another user.
paths:
'/api/v1/user/{userId}':
get:
tags:
- User
summary: Returns the user attributes
parameters:
- $ref: '#/components/parameters/UserId'
responses:
200:
description: Returns the user data for the given id.
content:
application/json:
schema:
type: object
properties:
success:
$ref: '#/components/schemas/Success'
result:
type: object
description: Contains the user data.
properties:
userId:
$ref: '#/components/schemas/UserId'
usernamme:
$ref: '#/components/schemas/Username'
jabberAddress:
$ref: '#/components/schemas/JabberAddress'
fingerprintIds:
type: array
items:
type: integer
description: A list of all fingerprint ids of the user.
example: [25, 42, 88]
default: []
default:
$ref: '#/components/responses/Error'
put:
tags:
- User
summary: Changes user attributes
parameters:
- $ref: '#/components/parameters/UserId'
requestBody:
required: true
description: Contains the json with the user attributes that have to be changed.
content:
application/json:
schema:
properties:
username:
$ref: '#/components/schemas/Username'
password:
$ref: '#/components/schemas/Password'
email:
$ref: '#/components/schemas/EmailAddress'
jabberAddress:
$ref: '#/components/schemas/JabberAddress'
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
delete:
tags:
- User
summary: Deletes a user
parameters:
- $ref: '#/components/parameters/UserId'
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
'/api/v1/user':
post:
tags:
- User
summary: Creates a new user
requestBody:
required: true
description: 'The attributes for the user to be created.'
content:
application/json:
schema:
properties:
username:
$ref: '#/components/schemas/Username'
password:
$ref: '#/components/schemas/Password'
email:
$ref: '#/components/schemas/EmailAddress'
jabberAddress:
$ref: '#/components/schemas/JabberAddress'
allOf:
- required:
- username
- password
- email
- jabberAddress
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
'/api/v1/user/session':
post:
tags:
- User
summary: Creates a user session (login)
requestBody:
required: true
content:
application/json:
schema:
properties:
username:
$ref: '#/components/schemas/Username'
password:
$ref: '#/components/schemas/Password'
allOf:
- required:
- username
- password
responses:
200:
description: Returns the success state and the user id for the created session.
content:
application/json:
schema:
properties:
success:
$ref: '#/components/schemas/Success'
userId:
$ref: '#/components/schemas/UserId'
default:
$ref: '#/components/responses/Error'
delete:
tags:
- User
summary: Deletes a user session (logout)
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
'/api/v1/fingerprint/{fingerprintId}/qr':
get:
tags:
- Fingerprint
summary: Returns the fingerprint's QR code
parameters:
- $ref: '#/components/parameters/FingerprintId'
responses:
200:
description: The QR code as SVG image
content:
application/svg+xml:
schema:
type: string
description: The xml of the SVG image.
default:
$ref: '#/components/responses/Error'
'/api/v1/fingerprint/{fingerprintId}':
get:
tags:
- Fingerprint
summary: Returns the attributes of the fingerprint
parameters:
- $ref: '#/components/parameters/FingerprintId'
responses:
200:
description: Returns the fingerprint for the given fingerprint id.
content:
application/json:
schema:
type: object
properties:
fingerprintId:
$ref: '#/components/schemas/FingerprintId'
fingerprint:
$ref: '#/components/schemas/Fingerprint'
userId:
type: integer
description: The id of the user that owns this fingerprint.
example: 25
default:
$ref: '#/components/responses/Error'
put:
tags:
- Fingerprint
summary: Changes the fingerprint
parameters:
- $ref: '#/components/parameters/FingerprintId'
requestBody:
content:
application/json:
schema:
properties:
fingerprint:
$ref: '#/components/schemas/Fingerprint'
required:
- fingerprint
responses:
200:
description: Returns the success state.
content:
application/json:
schema:
properties:
success:
type: boolean
description: 'Returns the success state of the fingerprint change.'
example: true
message:
type: string
description: 'Returns if no changes have been found inside the request body.'
example: 'Fingerprint did not differ from the stored. Nothing changed.'
required:
- success
default:
$ref: '#/components/responses/Error'
delete:
tags:
- Fingerprint
summary: Removes the fingerprint
parameters:
- $ref: '#/components/parameters/FingerprintId'
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
'/api/v1/fingerprint':
post:
tags:
- Fingerprint
summary: Creates a new fingerprint
requestBody:
content:
application/json:
schema:
description: The parameters for the new fingerprint to be created.
properties:
fingerprint:
$ref: '#/components/schemas/Fingerprint'
userId:
$ref: '#/components/schemas/UserId'
required:
- fingerprint
- userId
responses:
200:
description: Returns the confirmation and the id for the newly created fingerprint.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
description: Returns the success status.
example: true
fingerprintId:
type: integer
description: The id for the newly created fingerprint.
example: 42
default:
$ref: '#/components/responses/Error'
'/api/v1/sharing/{sharingId}':
get:
tags:
- Sharing
summary: Returns the sharing attributes
parameters:
- $ref: '#/components/parameters/SharingId'
responses:
200:
description: The JSON result for the sharing.
content:
application/json:
schema:
properties:
success:
$ref: '#/components/schemas/Success'
result:
type: object
properties:
sharingId:
$ref: '#/components/schemas/SharingId'
userId:
type: integer
description: The id of the user that shares his fingerprints.
example: 42
userSharedId:
$ref: '#/components/schemas/SharingUserId'
default:
$ref: '#/components/responses/Error'
delete:
tags:
- Sharing
summary: Removes the specified sharing
parameters:
- $ref: '#/components/parameters/SharingId'
responses:
200:
$ref: '#/components/responses/Success'
default:
$ref: '#/components/responses/Error'
'/api/v1/sharing':
post:
tags:
- Sharing
summary: Creates a new sharing
requestBody:
description: Contains the attributes for the sharing to be created.
content:
application/json:
schema:
type: object
properties:
userId:
type: integer
description: The id of the user that shares his fingerprints.
example: 42
userSharedId:
$ref: '#/components/schemas/SharingUserId'
responses:
200:
description: Returns the success flag and the id for the newly created sharing.
content:
application/json:
schema:
type: object
properties:
success:
$ref: '#/components/schemas/Success'
sharingId:
$ref: '#/components/schemas/SharingId'
default:
$ref: '#/components/responses/Error'
components:
parameters:
FingerprintId:
name: fingerprintId
in: path
required: true
schema:
$ref: '#/components/schemas/FingerprintId'
SharingId:
name: sharingId
in: path
required: true
schema:
$ref: '#/components/schemas/SharingId'
UserId:
name: userId
in: path
required: true
schema:
$ref: '#/components/schemas/UserId'
responses:
Success:
description: Returns that the request could be handled successfully.
content:
application/json:
schema:
properties:
success:
$ref: '#/components/schemas/Success'
Error:
description: Returns a JSON object that describes the error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
properties:
success:
type: boolean
description: Returns that the request couldn't be handled.
example: false
message:
type: string
description: Description of the error that occured.
example: Something extremely terrible happened!
Success:
type: boolean
description: Returns if the request could be handled.
example: true
UserId:
type: integer
description: The id of the user in question.
example: 42
Username:
type: string
description: The login name for the given user.
example: Kevin42
Password:
type: string
description: The password for the user login.
example: '12345'
JabberAddress:
type: string
description: The address of the jabber account.
example: donalt@jabber.de
EmailAddress:
type: string
description: The registered email address for the user.
example: kevin@gmail.com
FingerprintId:
type: integer
description: The id that identifies the fingerprint.
example: 42
readOnly: true
Fingerprint:
type: string
description: The jabber fingerprint
example: '5BDF1668E59F2184582591699F55D9158DEF400A48772887A8F61531ED36B2A'
SharingId:
type: integer
description: The id for the sharing.
example: 42
readOnly: true
SharingUserId:
type: integer
description: The id of the user who is allowed to display the fingerprints.
example: 25

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
final class FingerprintPutController extends AbstractController
{
protected string $route = '/api/v1/fingerprint/{fingerprintId}';
protected array $mandatoryAttributes = [
'fingerprint',
];
private int $fingerprintId;

View File

@ -17,9 +17,15 @@ final class UserGetController extends AbstractController
public function handle(): void
{
$user = new User($this->userId);
$this->response = new ApiJsonResponse();
$this->response = new ApiJsonResponse();
$this->response->setResult($user);
try {
$user = new User($this->userId);
$this->response->setResult($user);
} catch (Throwable $e) {
$this->response->setParameter('success', false);
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::INTERNAL_ERROR);
$this->response->setMessage($e->getMessage());
}
}
}

View File

@ -2,9 +2,9 @@
declare(strict_types=1);
final class UserLogoutPutController extends AbstractController
final class UserSessionDeleteController extends AbstractController
{
protected string $route = '/api/v1/user/logout';
protected string $route = '/api/v1/user/session';
public function handle(): void
{
@ -15,7 +15,7 @@ final class UserLogoutPutController extends AbstractController
if (!$session->IsLoggedIn()) {
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
$this->response->setParameter('success', false);
$this->response->setMessage('You were not logged in!');
$this->response->setMessage('No session to delete!');
return;
}

View File

@ -2,9 +2,9 @@
declare(strict_types=1);
final class UserLoginPutController extends AbstractController
final class UserSessionPostController extends AbstractController
{
protected string $route = '/api/v1/user/login';
protected string $route = '/api/v1/user/session';
protected array $mandatoryAttributes = [
'username',
'password',
@ -39,5 +39,6 @@ final class UserLoginPutController extends AbstractController
}
$this->response = new ApiJsonResponse();
$this->response->setParameter('userId', $session->getUserId());
}
}

View File

@ -19,7 +19,10 @@ final class MySqlDatabase implements DatabaseInterface
try {
$this->connection = new PDO("mysql:host=$hostname;dbname=$database", $user, $password);
} catch (Throwable $e) {
throw new DatabaseException('Couldn\'t connect to the database!');
throw new DatabaseException(
'Couldn\'t connect to the database!',
ServerStatus::INTERNAL_ERROR
);
}
}
@ -138,7 +141,10 @@ final class MySqlDatabase implements DatabaseInterface
try {
$this->Query($query, $conditions);
} catch (Throwable $e) {
return [];
throw new DatabaseException(
$e->getMessage(),
ServerStatus::INTERNAL_ERROR
);
}
return $this->getResult();

View File

@ -42,7 +42,7 @@ final class Session
}
$this->SetBool(self::IS_LOGGED_IN, true);
$this->SetInt(self::USER_ID, $user->getPrimaryKey());
$this->SetInt(self::USER_ID, $user->getUserId());
$this->SetString(self::USERNAME, $user->getUsername());
$this->SetString(self::EMAIL, $user->getEmail());
$this->SetString(self::JABBER_ADDRESS, $user->getJabberAddress());
@ -90,7 +90,7 @@ final class Session
return $this->HasValue($key) ? (bool)$_SESSION[$key] : null;
}
public function GetAccountId(): ?int
public function getUserId(): ?int
{
return $this->GetInt(self::USER_ID);
}

View File

@ -67,7 +67,10 @@ abstract class Table
$type = self::TYPE_BOOL;
break;
default:
throw new Exception(sprintf('Type %s of field %s couldn\'t be handled', $sqlType, $field['Field']));
throw new DatabaseException(
sprintf('Type %s of field %s couldn\'t be handled', $sqlType, $field['Field']),
ServerStatus::INTERNAL_ERROR
);
}
$this->addField($field['Field'], $type);
@ -94,8 +97,9 @@ abstract class Table
protected function addField(string $name, int $type): void
{
if (!self::IsValidType($type)) {
throw new Exception(
sprintf('Field %s has invalid type of %s!', $name, $type)
throw new DatabaseException(
sprintf('Field %s has invalid type of %s!', $name, $type),
ServerStatus::INTERNAL_ERROR
);
}
@ -104,19 +108,18 @@ abstract class Table
protected function loadById($id): void
{
try {
$this->database->Query(
sprintf('SELECT * FROM %s WHERE %s = :id', $this->tableName, $this->primaryKey),
['id' => $id]
);
} catch (Throwable $e) {
throw new Exception();
}
$this->database->Query(
sprintf('SELECT * FROM %s WHERE %s = :id', $this->tableName, $this->primaryKey),
['id' => $id]
);
$result = $this->database->getResult();
if (count($result) === 0) {
throw new Exception(sprintf('No %s with id %d found!', $this->tableName, $id));
throw new DatabaseException(
sprintf('No %s with id %d found!', $this->tableName, $id),
ServerStatus::BAD_REQUEST
);
}
foreach ($result[0] as $field => $value) {
@ -131,11 +134,7 @@ abstract class Table
public function Delete(): void
{
try {
$this->database->Delete($this->tableName, [$this->primaryKey => $this->getPrimaryKey()]);
} catch (Throwable $e) {
throw new Exception();
}
$this->database->Delete($this->tableName, [$this->primaryKey => $this->getPrimaryKey()]);
foreach ($this->GetAllFieldNames() as $field) {
$this->fields[$field][self::VALUE] = null;
@ -157,7 +156,10 @@ abstract class Table
protected function setField(string $name, $value): void
{
if (!$this->HasField($name)) {
throw new Exception(sprintf('Field %s doesn\'t exist!', $name));
throw new DatabaseException(
sprintf('Field %s doesn\'t exist!', $name),
ServerStatus::INTERNAL_ERROR
);
}
if ($value === null) {
@ -178,8 +180,11 @@ abstract class Table
case self::TYPE_DATETIME:
try {
$this->fields[$name][self::VALUE] = new DateTime((string)$value);
} catch (Exception $e) {
throw new Exception();
} catch (Throwable $e) {
throw new DatabaseException(
$e->getMessage(),
ServerStatus::INTERNAL_ERROR
);
}
return;
case self::TYPE_BOOL:
@ -254,7 +259,10 @@ abstract class Table
protected function saveWithManualId(array $fields): void
{
if ($this->getField($this->primaryKey) === null) {
throw new Exception('Manual primary key must not be null!');
throw new DatabaseException(
'Manual primary key must not be null!',
ServerStatus::INTERNAL_ERROR
);
}
$hasKey = (bool)$this->database->Count(

View File

@ -73,11 +73,11 @@ final class User extends MySqlTable implements JsonSerializable
$databaseGiven = false;
}
if ($database->Count(self::class) === 0) {
if ($database->Count(self::class, [self::FIELD_USERNAME => $username]) === 0) {
throw new UserException(sprintf('No user with name %s found!', $username));
}
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_USERNAME => $username]);
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_USERNAME => $username])[0][self::FIELD_ID];
$user = $databaseGiven ? new User((int)$id, $database) : new User((int)$id);

View File