-
Notifications
You must be signed in to change notification settings - Fork 249
Api tokens #1965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Api tokens #1965
Changes from all commits
7877293
49f6918
72fab9e
6f737d5
9cd4fe8
3b5d5c0
9f624a6
58a555c
fdb90bb
25da3dc
dfc2b21
e12e54f
04f22fa
8715c3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| <?php | ||
|
|
||
| namespace Hashtopolis\dba\models; | ||
|
|
||
| use Hashtopolis\dba\AbstractModel; | ||
|
|
||
| class JwtApiKey extends AbstractModel { | ||
| private ?int $jwtApiKeyId; | ||
| private ?int $startValid; | ||
| private ?int $endValid; | ||
| private ?int $userId; | ||
| private ?int $isRevoked; | ||
|
|
||
| function __construct(?int $jwtApiKeyId, ?int $startValid, ?int $endValid, ?int $userId, ?int $isRevoked) { | ||
| $this->jwtApiKeyId = $jwtApiKeyId; | ||
| $this->startValid = $startValid; | ||
| $this->endValid = $endValid; | ||
| $this->userId = $userId; | ||
| $this->isRevoked = $isRevoked; | ||
| } | ||
|
|
||
| function getKeyValueDict(): array { | ||
| $dict = array(); | ||
| $dict['jwtApiKeyId'] = $this->jwtApiKeyId; | ||
| $dict['startValid'] = $this->startValid; | ||
| $dict['endValid'] = $this->endValid; | ||
| $dict['userId'] = $this->userId; | ||
| $dict['isRevoked'] = $this->isRevoked; | ||
|
|
||
| return $dict; | ||
| } | ||
|
|
||
| static function getFeatures(): array { | ||
| $dict = array(); | ||
| $dict['jwtApiKeyId'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => True, "protected" => True, "private" => False, "alias" => "jwtApiKeyId", "public" => False, "dba_mapping" => False]; | ||
| $dict['startValid'] = ['read_only' => True, "type" => "int64", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "startValid", "public" => False, "dba_mapping" => False]; | ||
| $dict['endValid'] = ['read_only' => True, "type" => "int64", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "endValid", "public" => False, "dba_mapping" => False]; | ||
| $dict['userId'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "userId", "public" => False, "dba_mapping" => False]; | ||
| $dict['isRevoked'] = ['read_only' => False, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "isRevoked", "public" => False, "dba_mapping" => False]; | ||
|
|
||
| return $dict; | ||
| } | ||
|
|
||
| function getPrimaryKey(): string { | ||
| return "jwtApiKeyId"; | ||
| } | ||
|
|
||
| function getPrimaryKeyValue(): ?int { | ||
| return $this->jwtApiKeyId; | ||
| } | ||
|
|
||
| function getId(): ?int { | ||
| return $this->jwtApiKeyId; | ||
| } | ||
|
|
||
| function setId($id): void { | ||
| $this->jwtApiKeyId = $id; | ||
| } | ||
|
|
||
| /** | ||
| * Used to serialize the data contained in the model | ||
| * @return array | ||
| */ | ||
| public function expose(): array { | ||
| return get_object_vars($this); | ||
| } | ||
|
|
||
| function getStartValid(): ?int { | ||
| return $this->startValid; | ||
| } | ||
|
|
||
| function setStartValid(?int $startValid): void { | ||
| $this->startValid = $startValid; | ||
| } | ||
|
|
||
| function getEndValid(): ?int { | ||
| return $this->endValid; | ||
| } | ||
|
|
||
| function setEndValid(?int $endValid): void { | ||
| $this->endValid = $endValid; | ||
| } | ||
|
|
||
| function getUserId(): ?int { | ||
| return $this->userId; | ||
| } | ||
|
|
||
| function setUserId(?int $userId): void { | ||
| $this->userId = $userId; | ||
| } | ||
|
|
||
| function getIsRevoked(): ?int { | ||
| return $this->isRevoked; | ||
| } | ||
|
|
||
| function setIsRevoked(?int $isRevoked): void { | ||
| $this->isRevoked = $isRevoked; | ||
| } | ||
|
|
||
| const JWT_API_KEY_ID = "jwtApiKeyId"; | ||
| const START_VALID = "startValid"; | ||
| const END_VALID = "endValid"; | ||
| const USER_ID = "userId"; | ||
| const IS_REVOKED = "isRevoked"; | ||
|
|
||
| const PERM_CREATE = "permJwtApiKeyCreate"; | ||
| const PERM_READ = "permJwtApiKeyRead"; | ||
| const PERM_UPDATE = "permJwtApiKeyUpdate"; | ||
| const PERM_DELETE = "permJwtApiKeyDelete"; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| <?php | ||
|
|
||
| namespace Hashtopolis\dba\models; | ||
|
|
||
| use Hashtopolis\dba\AbstractModelFactory; | ||
| use Hashtopolis\dba\Util; | ||
|
|
||
| class JwtApiKeyFactory extends AbstractModelFactory { | ||
| function getModelName(): string { | ||
| return "JwtApiKey"; | ||
| } | ||
|
|
||
| function getModelTable(): string { | ||
| return "JwtApiKey"; | ||
| } | ||
|
|
||
| function isMapping(): bool { | ||
| return False; | ||
| } | ||
|
|
||
| function isCachable(): bool { | ||
| return false; | ||
| } | ||
|
|
||
| function getCacheValidTime(): int { | ||
| return -1; | ||
| } | ||
|
|
||
| /** | ||
| * @return JwtApiKey | ||
| */ | ||
| function getNullObject(): JwtApiKey { | ||
| return new JwtApiKey(-1, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
| * @param string $pk | ||
| * @param array $dict | ||
| * @return JwtApiKey | ||
| */ | ||
| function createObjectFromDict($pk, $dict): JwtApiKey { | ||
| $conv = []; | ||
| foreach ($dict as $key => $val) { | ||
| $conv[strtolower($key)] = $val; | ||
| } | ||
| $dict = $conv; | ||
| return new JwtApiKey($dict['jwtapikeyid'], $dict['startvalid'], $dict['endvalid'], $dict['userid'], $dict['isrevoked']); | ||
| } | ||
|
|
||
| /** | ||
| * @param array $options | ||
| * @param bool $single | ||
| * @return JwtApiKey|JwtApiKey[] | ||
| */ | ||
| function filter(array $options, bool $single = false): JwtApiKey|array|null { | ||
| $join = false; | ||
| if (array_key_exists('join', $options)) { | ||
| $join = true; | ||
| } | ||
| if ($single) { | ||
| if ($join) { | ||
| return parent::filter($options, $single); | ||
| } | ||
| return Util::cast(parent::filter($options, $single), JwtApiKey::class); | ||
| } | ||
| $objects = parent::filter($options, $single); | ||
| if ($join) { | ||
| return $objects; | ||
| } | ||
| $models = array(); | ||
| foreach ($objects as $object) { | ||
| $models[] = Util::cast($object, JwtApiKey::class); | ||
| } | ||
| return $models; | ||
| } | ||
|
|
||
| /** | ||
| * @param string $pk | ||
| * @return ?JwtApiKey | ||
| */ | ||
| function get($pk): ?JwtApiKey { | ||
| return Util::cast(parent::get($pk), JwtApiKey::class); | ||
| } | ||
|
|
||
| /** | ||
| * @param JwtApiKey $model | ||
| * @return JwtApiKey | ||
| */ | ||
| function save($model): JwtApiKey { | ||
| return Util::cast(parent::save($model), JwtApiKey::class); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,34 +13,50 @@ | |
| use Hashtopolis\dba\models\User; | ||
| use Hashtopolis\dba\Factory; | ||
| use Firebase\JWT\JWK; | ||
| use Hashtopolis\dba\JoinFilter; | ||
| use Hashtopolis\dba\models\RightGroup; | ||
| use Hashtopolis\inc\apiv2\error\HttpForbidden; | ||
|
|
||
| require_once(dirname(__FILE__) . "/../../startup/include.php"); | ||
|
|
||
| const USER_AUD = "user_hashtopolis"; | ||
| function generateTokenForUser(Request $request, string $userName, int $expires) { | ||
| $jti = bin2hex(random_bytes(16)); | ||
|
|
||
| $requested_scopes = $request->getParsedBody() ?: ["todo.all"]; | ||
|
|
||
| $valid_scopes = [ | ||
| "todo.create", | ||
| "todo.read", | ||
| "todo.update", | ||
| "todo.delete", | ||
| "todo.list", | ||
| "todo.all" | ||
| ]; | ||
|
|
||
| $scopes = array_filter($requested_scopes, function ($needle) use ($valid_scopes) { | ||
| return in_array($needle, $valid_scopes); | ||
| }); | ||
| // FIXME: This is duplicated and should be passed by HttpBasicMiddleware | ||
| $filter = new QueryFilter(User::USERNAME, $userName, "="); | ||
| $check = Factory::getUserFactory()->filter([Factory::FILTER => $filter]); | ||
| $jF = new JoinFilter(Factory::getRightGroupFactory(), User::RIGHT_GROUP_ID, RightGroup::RIGHT_GROUP_ID); | ||
| $joined = Factory::getUserFactory()->filter([Factory::FILTER => $filter, Factory::JOIN => $jF]); | ||
| /** @var User[] $check */ | ||
| $check = $joined[Factory::getUserFactory()->getModelName()]; | ||
| if (count($check) === 0) { | ||
| throw new HttpError("No user with this userName in the database"); | ||
| } | ||
| $user = $check[0]; | ||
| if ($user->getIsValid() !== 1) { | ||
| throw new HttpForbidden("User is set to invalid"); | ||
| } | ||
|
|
||
| if (empty($user)) { | ||
| throw new HttpError("No user with this userName in the database"); | ||
| /** @var RightGroup[] $groupArray */ | ||
| $groupArray = $joined[Factory::getRightGroupFactory()->getModelName()]; | ||
| if (count($groupArray) === 0) { | ||
| throw new HttpError("No rightgroup found for this user"); | ||
| } | ||
| $group = $groupArray[0]; | ||
| $scopes = $group->getPermissions(); | ||
|
|
||
|
Comment on lines
+31
to
+46
|
||
| // $requested_scopes = $request->getParsedBody() ?: ["todo.all"]; | ||
| // $valid_scopes = [ | ||
| // "todo.create", | ||
| // "todo.read", | ||
| // "todo.update", | ||
| // "todo.delete", | ||
| // "todo.list", | ||
| // "todo.all" | ||
| // ]; | ||
|
|
||
| // $scopes = array_filter($requested_scopes, function ($needle) use ($valid_scopes) { | ||
| // return in_array($needle, $valid_scopes); | ||
| // }); | ||
|
|
||
| $secret = StartupConfig::getInstance()->getPepper(0); | ||
| $now = new DateTime(); | ||
|
|
@@ -52,7 +68,8 @@ function generateTokenForUser(Request $request, string $userName, int $expires) | |
| "userId" => $user->getId(), | ||
| "scope" => $scopes, | ||
| "iss" => "Hashtopolis", | ||
| "kid" => hash("sha256", $secret) | ||
| "kid" => hash("sha256", $secret), | ||
| "aud" => USER_AUD | ||
| ]; | ||
|
|
||
| $token = JWT::encode($payload, $secret, "HS256"); | ||
|
|
@@ -75,8 +92,7 @@ function extractBearerToken(Request $request): ?string { | |
| } | ||
|
|
||
| // Exchanges an oauth token for a application JWT token | ||
| use Slim\App; | ||
| /** @var App $app */ | ||
| /** @var \Slim\App $app */ | ||
| $app->group("/api/v2/auth/oauth-token", function (RouteCollectorProxy $group) { | ||
|
|
||
| $group->post('', function (Request $request, Response $response, array $args): Response { | ||
|
|
@@ -159,7 +175,8 @@ function extractBearerToken(Request $request): ?string { | |
| "userId" => $request->getAttribute(('userId')), | ||
| "scope" => $request->getAttribute("scope"), | ||
| "iss" => "Hashtopolis", | ||
| "kid" => hash("sha256", $secret) | ||
| "kid" => hash("sha256", $secret), | ||
| "aud" => USER_AUD | ||
| ]; | ||
|
|
||
| $token = JWT::encode($payload, $secret, "HS256"); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.