Skip to content

Commit

Permalink
Merge pull request #387 from jjrom/develop
Browse files Browse the repository at this point in the history
[MAJOR] Add groups and rights API
  • Loading branch information
jjrom authored Apr 8, 2024
2 parents 770a8ab + c220f8d commit 56b3d50
Show file tree
Hide file tree
Showing 40 changed files with 4,502 additions and 1,032 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Then get the collections list :

curl "http://localhost:5252/collections"

### Collection aliases
A collection can contain an array of *aliases* (see [./examples/collections/L8.json](./examples/collections/L8.json#L3-L5) for instance). These aliases are alternate names to the collection id. Thus {collectionId} value in /collections/{collectionId}/* endpoints can use the original collection id or one of its aliases.

Note that id and aliases must be unique in the database. As a consequence, you cannot create a new collection or set an alias to an existing collection that as the same value of one of the aliases of an existing collection.

## Ingest a feature
To ingest a feature using the default **ADMIN_USER_NAME** and **ADMIN_USER_PASSWORD** (see [config.env](config.env)) :

Expand All @@ -60,6 +65,9 @@ Then get the feature :

curl "http://localhost:5252/collections/S2/items/S2A_MSIL1C_20190611T160901_N0207_R140_T23XMD_20190611T193040"

## Users, groups and rights
See [./USERS.md](./USERS.md)

# TL;DR
The [INSTALLATION.md](INSTALLATION.md) file provides additional information on the installation process.

Expand All @@ -68,7 +76,7 @@ The [INSTALLATION.md](INSTALLATION.md) file provides additional information on t
Here are some projects that use resto.

* [SnapPlanet](https://snapplanet.io)
* [Copernicus Data Space Ecosystem](https://dataspace.copernicus.eu/)
* [European Digital Twin of the Ocean](https://www.edito.eu)
* [CREODIAS](https://creodias.eu/eo-data-finder-api-manual)
* [Rocket - The Earth in your pocket](https://rocket.snapplanet.io)
* [The French Sentinel Data Processing center](https://peps.cnes.fr/rocket/#/home)
Expand All @@ -86,12 +94,6 @@ Here are some projects that use resto.
If you plan to use resto and would like to have your project added to this list, feel free to contact [support](#support)

# <a name="support"></a>Support
resto is developped and maintained by [jeobrowser](https://mapshup.com).

For questions, support or anything related to resto feel free to contact
resto is developped and maintained by [jeobrowser](https://snapplanet.io).

jeobrowser
50 quai de Tounis
31000 Toulouse
Tel : +33 6 19 59 17 35
email : jerome.gasperi@gmail.com
For questions, support or anything related to resto feel free to contact *jerome[dot]gasperi[at]gmail[dot]com*
176 changes: 176 additions & 0 deletions USERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Users, rights and groups
resto provide a user authentication and authorization mechanism allowing to manage access to ressources in particular to authorize CRUD operations (Create, Read, Update, Delete) on collections and features.

## Users
On the first launch of resto, one user (admin) is created with a user *{userId}* equals to **100**. This user is automatically added to the **admin group** (see chapter on groups below).

### Add a new user
The following example shows how to add a new user. When adding a new user, it will be automatically associated with default rights (see chapter on rights below).

# Add a new user
curl -X POST -d@examples/users/johnDoe.json "http://localhost:5252/users"

The above command should returns an HTTP 200 response including the newly create user profile

{
"status":"success",
"message":"User johndoe@localhost created and activated",
"profile":{
"id":"224468756040777732",
"email":"johndoe@localhost",
"name":"John Doe",
"firstname":"John",
"lastname":"Doe",
"lang":"en",
"topics":null,
"picture":"https://robohash.org/a6b506f3dae99e4c35ae50ae240e8f5d?gravatar=hashed&bgset=any&size=400x400",
"registrationdate":"2024-04-08 07:18:19.77169",
"activated":1,
"followers":0,
"followings":0
}
}

Notes :

* An unique *id* is created for the user
* The *activated* value set to 1. This means that the user is created and validated i.e. you can use authenticate with this user within resto. If you want to check for email address before allowing user to authenticate to resto, you have to set the **USER_AUTOVALIDATION** environment value to *false* in [config.env](./config.env). In this case, the user will receive an email including a validation link. The *activated* value will be set to 1 upon user's validation link resolution.

### Get an authorization token (optional)
To authenticate to resto endpoint, you can either provide the email/password of an existing user or an authentication token.

You can generate a bearer authentication token valid for 100 days for the above user with the following command:

./scripts/generateAuthToken -i 224468756040777732 -d 100

The result should be:

{"userId":"224468756040777732","duration":100,"valid_until":"2024-07-17T09:41:49","token":"eyJzdWIiOiIyMjQ0Njg3NTYwNDA3Nzc3MzIiLCJpYXQiOjE3MTI1NjIxMDksImV4cCI6MTcyMTIwMjEwOX0.XatRV4bLbuRyvsQrL2etPAumpPPg5SK2h-7qVRrPub4"}

The token can be used to request authenticated endpoint, for instance to get the user profile:

# Using email/password
curl "http://johnDoe%40localhost:dummy@localhost:5252/users/224468756040777732"

# Using bearer token
export JOHN_DOE_BEARER=eyJzdWIiOiIyMjQ0Njg3NTYwNDA3Nzc3MzIiLCJpYXQiOjE3MTI1NjIxMDksImV4cCI6MTcyMTIwMjEwOX0.XatRV4bLbuRyvsQrL2etPAumpPPg5SK2h-7qVRrPub4
curl -H "Authorization: Bearer ${JOHN_DOE_BEARER}" "http://localhost:5252/users/224468756040777732"

## Rights
The rights defines access to resto ressources in particular to authorize CRUD operations (Create, Read, Update, Delete) on collections and features.

rights are defined as boolean properties within a JSON object. The default user's rights are the following:

{
// If true the user can create a collection
"createCollection": false,

// If true the user can delete a collection he owns
"deleteCollection": true,

// If true the user can update a collection he owns
"updateCollection": true,

// If true the user can delete any collection whether he owns it or not
"deleteAnyCollection": false,

// If true the user can update any collection whether he owns it or not
"updateAnyCollection": false,
// If true the user can add a feature to a collection owns
"createFeature": true,

// If true the user can delete a feature he owns
"deleteFeature": true,

// If true the user can update a feature he owns
"updateFeature": true,
// If true the user can add a feature to any collection whether he owns it or not
"createAnyFeature": false,

// If true the user can delete any feature whether he owns it or not
"deleteAnyFeature": false,

// If true the user can update any feature whether he owns it or not
"updateAnyFeature": false

}

### Get user rights
To get the rights for John Doe:

curl -H "Authorization: Bearer ${JOHN_DOE_BEARER}" "http://localhost:5252/users/224468756040777732/rights"

The result should be :

{"rights":{"createCollection":false,"deleteCollection":true,"updateCollection":true,"deleteAnyCollection":false,"updateAnyCollection":false,"createFeature":true,"updateFeature":true,"deleteFeature":true,"createAnyFeature":false,"deleteAnyFeature":false,"updateAnyFeature":false,"downloadFeature":false}}

### Set user rights
Only a user in the **admin group** (see chapter on groups below) can set the rights of a user.

# Allow John Doe to create collection
curl -X POST -d@examples/users/johnDoe_rights.json "http://admin:admin@localhost:5252/users/224468756040777732/rights"

The result should returns :

{"status":"success","message":"Rights set","rights":{"createCollection":true}}

Note that existing rights are not deleted when setting rights but are merged with input rights.

## Groups
groups can be used to share rights among group members.

On the first launch of resto, two groups are created :

* The *admin group* identified by id **0**.
* The *default group* identifier by id **100**

All users are automatically added to the default group

### Add a group
Any user can add a group. Note that the group name must be unique.

# Create dummy group
curl -X POST -d@examples/users/dummyGroup.json "http://johnDoe%40localhost:dummy@localhost:5252/groups"

The result should be

{"status":"success","message":"Group created","id":103,"name":"My first group","owner":"224468756040777732"}

### Set group rights
Only a user in the **admin group** can set the rights for a group

# Set rights for dummy group allowing members to createAnyFeature
curl -X POST -d@examples/users/dummyGroup_rights.json "http://admin:admin@localhost:5252/groups/103/rights"

The result should returns :

{"status":"success","message":"Rights set","rights":{"createAnyFeature":true}}

Note that existing rights are not deleted when setting rights but are merged with input rights.

### Add user to a group
Only a user in the **admin group** or the owner of the group can add user to a group

# Add John Doe in group dummyGroup
curl -X POST -d@examples/users/dummyGroup_addJohnDoe.json "http://admin:admin@localhost:5252/groups/103/users"

# Consequently, John Doe's rights now includes rights from its groups
curl -H "Authorization: Bearer ${JOHN_DOE_BEARER}" "http://localhost:5252/users/224468756040777732/rights"

# Result of previous request shows that John Doe can now createAnyFeature since he is in dummyGroup
# {"rights":{"createCollection":true,"deleteCollection":true,"updateCollection":true,"deleteAnyCollection":false,"updateAnyCollection":false,"createFeature":true,"updateFeature":true,"deleteFeature":true,"createAnyFeature":true,"deleteAnyFeature":false,"updateAnyFeature":false,"downloadFeature":false}

### Remove user from a group
Only a user in the **admin group** or the owner of the group can remove a user from a group

# Remove John Doe from dummyGroup
curl -X DELETE "http://admin:admin@localhost:5252/groups/103/users/224468756040777732"

# Consequently, John Doe's rights do not include anymore rights from dummyGroup
curl -H "Authorization: Bearer ${JOHN_DOE_BEARER}" "http://localhost:5252/users/224468756040777732/rights"

# {"rights":{"createCollection":true,"deleteCollection":true,"updateCollection":true,"deleteAnyCollection":false,"updateAnyCollection":false,"createFeature":true,"updateFeature":true,"deleteFeature":true,"createAnyFeature":false,"deleteAnyFeature":false,"updateAnyFeature":false,"downloadFeature":false}

## Ownership and visibility
96 changes: 58 additions & 38 deletions app/resto/core/RestoCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
* description="Detailed multi-line description to fully explain the Collection. CommonMark 0.29 syntax MAY be used for rich text representation."
* ),
* @OA\Property(
* property="aliases",
* type="array",
* description="Alias names for this collection. Each alias must be unique and not be the same as an already existing collection name",
* @OA\Items(
* type="string",
* )
* ),
* @OA\Property(
* property="version",
* type="string",
* description="Version of the collection."
Expand Down Expand Up @@ -276,6 +284,14 @@
* )
* ),
* @OA\Property(
* property="aliases",
* type="array",
* description="Alias names for this collection. Each alias must be unique and not be the same as an already existing collection name",
* @OA\Items(
* type="string",
* )
* ),
* @OA\Property(
* property="license",
* type="string",
* enum={"proprietary", "various", "<license id>"},
Expand Down Expand Up @@ -570,6 +586,7 @@ class RestoCollection
/*
* [STAC] Collection root attributes
*/
public $aliases = array();
public $visibility = RestoConstants::GROUP_DEFAULT_ID;
public $version = '1.0.0';
public $license = 'proprietary';
Expand Down Expand Up @@ -782,26 +799,31 @@ class RestoCollection
* Properties that are not stored in properties column
*/
private $notInProperties = array(
'id',
'type',
'title',
'aliases',
'assets',
'description',
'stac_version',
'stac_extension',
'extent',
'model',
'version',
'visibility',
'rights',
'id',
'keywords',
'license',
'links',
'model',
'osDescription',
'providers',
'rights',
'assets',
'keywords'
'stac_extension',
'stac_version',
'title',
'type',
'version',
'visibility'
);

/**
* Avoid call to database when object is already loaded
*/
private $isLoaded = false;

/**
* Constructor
*
Expand All @@ -811,17 +833,6 @@ class RestoCollection
*/
public function __construct($id, $context, $user)
{
/*
* Remove collection name constraint
if (isset($id)) {
// Collection identifier is an alphanumeric string without special characters
if (preg_match("/^[a-zA-Z0-9\-_\.\:]+$/", $id) !== 1) {
RestoLogUtil::httpError(400, 'Collection identifier must be an alphanumeric string containing only [a-zA-Z0-9\-_.:]');
}
$this->id = $id;
}*/

$this->id = $id;
$this->context = $context;
$this->user = $user;
Expand All @@ -837,28 +848,35 @@ public function __construct($id, $context, $user)
*/
public function load($object = null, $modelName = null)
{

if (isset($object)) {
return $this->loadFromJSON($object, $modelName);
}

$cacheKey = 'collection:' . $this->id;
$collectionObject = $this->context->fromCache($cacheKey);
if ( !$this->isLoaded ) {

$this->isLoaded = true;

$cacheKey = 'collection:' . $this->id;
$collectionObject = $this->context->fromCache($cacheKey);

if (! isset($collectionObject)) {
$collectionObject = (new CollectionsFunctions($this->context->dbDriver))->getCollectionDescription($this->id);

if (! isset($collectionObject)) {
return RestoLogUtil::httpError(404);
}

if (! isset($collectionObject)) {
$collectionObject = (new CollectionsFunctions($this->context->dbDriver))->getCollectionDescription($this->id);
$this->context->toCache($cacheKey, $collectionObject);
}

if (! isset($collectionObject)) {
return RestoLogUtil::httpError(404);
foreach ($collectionObject as $key => $value) {
$this->$key = $key === 'model' ? new $value(array(
'collectionId' => $this->id,
'addons' => $this->context->addons
)) : $value;
}

$this->context->toCache($cacheKey, $collectionObject);
}

foreach ($collectionObject as $key => $value) {
$this->$key = $key === 'model' ? new $value(array(
'collectionId' => $this->id,
'addons' => $this->context->addons
)) : $value;
}

return $this;
Expand Down Expand Up @@ -931,6 +949,7 @@ public function toArray($options = array())
'title' => $osDescription['LongName'] ?? $osDescription['ShortName'],
'version' => $this->version ?? null,
'description' => $osDescription['Description'],
'aliases' => $this->aliases ?? array(),
'license' => $this->license,
'extent' => $this->extent,
'links' => array_merge(
Expand Down Expand Up @@ -1092,7 +1111,7 @@ private function loadFromJSON($object, $modelName = null)
/*
* Set values
*/
foreach (array_values(array('version', 'license', 'links', 'osDescription', 'providers', 'rights', 'assets', 'keywords', 'extent')) as $key) {
foreach (array_values(array('aliases', 'version', 'license', 'links', 'osDescription', 'providers', 'rights', 'assets', 'keywords', 'extent')) as $key) {
if (isset($object[$key])) {
$this->$key = $key === 'links' ? $this->cleanInputLinks($object['links']) : $object[$key];
}
Expand All @@ -1110,7 +1129,8 @@ private function loadFromJSON($object, $modelName = null)
}
}


$this->isLoaded = true;

return $this;
}

Expand Down
Loading

0 comments on commit 56b3d50

Please sign in to comment.