Skip to content

Commit

Permalink
Get Project Permissions (#1708)
Browse files Browse the repository at this point in the history
* feature (getPermissionsForProject) route for `getAdministrativePermissionsForProject` + e2e test

* feature (getPermissionsForProject) route for `getDefaultObjectAccessPermissionsForProject` + test data

* test (getPermissionsForProject) e2e test for getting doap of a project

* feature (getPermissionsForProject) class `PermissionsForProjectGet` request and response added to messages + unit tests

* feature (getPermissionsForProject) responder to get all permissions for a project + unit tests

* feature (getPermissionsForProject) route to get all permissions for a project + e2e test

* feature (getPermissionsForProject) test data for `getPermissionsForProject`

* docs (permissionOperation) documentation

* fix (getPermissionsForProject) the `systemUser` issue
  • Loading branch information
SepidehAlassi committed Sep 17, 2020
1 parent 4f57977 commit 3571dab
Show file tree
Hide file tree
Showing 9 changed files with 561 additions and 88 deletions.
2 changes: 1 addition & 1 deletion docs/03-apis/api-admin/groups.md
Expand Up @@ -25,7 +25,7 @@ License along with Knora. If not, see <http://www.gnu.org/licenses/>.

- `GET: /admin/groups` : return all groups
- `GET: /admin/groups/<groupIri>` : return single group identified by [IRI]
- `POST: /admin/groups` : create new group
- `POST: /admin/groups` : create a new group
- `PUT: /admin/groups/<groupIri>` : update groups's basic information
- `PUT: /admin/groups/<groupIri>/status` : update group's status
- `DELETE: /admin/groups/<groupIri>` : delete group (set status to false)
Expand Down
119 changes: 113 additions & 6 deletions docs/03-apis/api-admin/permissions.md
Expand Up @@ -19,12 +19,119 @@ License along with Knora. If not, see <http://www.gnu.org/licenses/>.

# Permissions Endpoint

- **Add/change/delete administrative permissions**:
##Permission Operations:

- Required permission: SystemAdmin / hasProjectAllAdminPermission
/ hasProjectRightsAdminPermission
**Note:** For the following operations, the requesting user must be either a `systemAdmin`or
a `projectAdmin`.

- **Add/change/delete default object access permissions**:
### Getting Permissions:
- `GET: /admin/permissions/<projectIri>` : return all permissions for a project.
As a response, the IRI and the type of all `permissions` of a project are returned.

- `GET: /admin/permissions/ap/<projectIri>`: return all administrative permissions
for a project. As a response, all `administrative_permissions` of a project are returned.

- `GET: /admin/permissions/ap/<projectIri>/<groupIri>`: return the administrative
permissions for a project group. As a response, the `administrative_permission` defined
for the group is returned.

- `GET: /admin/permissions/doap/<projectIri>`: return all default object access
permissions for a project. As a response, all `default_object_acces_permissions` of a
project are returned.

### Creating New Permissions:

- `POST: /admin/permissions/ap`: create a new administrative permission. The type of
permissions, the project and group to which the permission should be added must be
included in the request body, for example:

```json
{
"forGroup":"http://rdfh.ch/groups/0001/thing-searcher",
"forProject":"http://rdfh.ch/projects/0001",
"hasPermissions":[{"additionalInformation":null,"name":"ProjectAdminGroupAllPermission","permissionCode":null}]
}
```

In addition, in the body of the request, it is possible to specify a custom IRI (of [Knora IRI](knora-iris.md#iris-for-data) form) for a permission through
the `@id` attribute which will then be assigned to the permission; otherwise the permission will get a unique random IRI.
A custom permission IRI must be `http://rdfh.ch/permissions/PROJECT_SHORTCODE/` (where `PROJECT_SHORTCODE`
is the shortcode of the project that the permission belongs to), plus a custom ID string. For example:
```
"id": "http://rdfh.ch/permissions/0001/AP-with-customIri",
```

As a response, the created administrative permission and its IRI are returned as below:

```json
{
"administrative_permission": {
"forGroup": "http://rdfh.ch/groups/0001/thing-searcher",
"forProject": "http://rdfh.ch/projects/0001",
"hasPermissions": [
{
"additionalInformation": null,
"name": "ProjectAdminGroupAllPermission",
"permissionCode": null
}
],
"iri": "http://rdfh.ch/permissions/0001/mFlyBEiMQtGzwy_hK0M-Ow"
}
}
```

- `POST: /admin/permissions/doap` : create a new default object access permission.
A single instance of `knora-admin:DefaultObjectAccessPermission` must
always reference a project, but can only reference **either** a group
(`knora-admin:forGroup` property), a resource class
(`knora-admin:forResourceClass`), a property (`knora-admin:forProperty`),
or a combination of resource class **and** property. For example, to create a new
default object access permission for a group of a project the request body would be

```
{
"forGroup":"http://rdfh.ch/groups/0001/thing-searcher",
"forProject":"http://rdfh.ch/projects/0001",
"forProperty":null,
"forResourceClass":null,
"hasPermissions":[{"additionalInformation":"http://www.knora.org/ontology/knora-admin#ProjectMember","name":"D","permissionCode":7}]
}
```

Similar to the previous case a custom IRI can be assigned to a permission through
the `@id` attribute. The example below shows the request body to create a new default
object access permission with a custom IRI defined for a resource class
of a specific project:

```json
{
"id": "http://rdfh.ch/permissions/00FF/DOAP-with-customIri",
"forGroup":null,
"forProject":"http://rdfh.ch/projects/00FF",
"forProperty":null,
"forResourceClass":"http://www.knora.org/ontology/00FF/images#bild",
"hasPermissions":[{"additionalInformation":"http://www.knora.org/ontology/knora-admin#ProjectMember","name":"D","permissionCode":7}]
}
```

The response contains the newly created permission and its IRI, as:

```json
{
"default_object_access_permission": {
"forGroup": null,
"forProject": "http://rdfh.ch/projects/00FF",
"forProperty": null,
"forResourceClass": "http://www.knora.org/ontology/00FF/images#bild",
"hasPermissions": [
{
"additionalInformation": "http://www.knora.org/ontology/knora-admin#ProjectMember",
"name": "D",
"permissionCode": 7
}
],
"iri": "http://rdfh.ch/permissions/00FF/DOAP-with-customIri"
}
}
```

- Required permission: SystemAdmin / hasProjectAllAdminPermission
/ hasProjectRightsAdminPermission
Expand Up @@ -130,6 +130,30 @@ case class PermissionDataGetADM(projectIris: Seq[IRI],
if (!requestingUser.isSystemUser) throw ForbiddenException("Permission data can only by queried by a SystemUser.")
}

/**
* A message that requests all permissions defined inside a project.
* A successful response will be a [[PermissionsForProjectGetResponseADM]].
*
* @param projectIri the project for which the permissions are queried.
* @param requestingUser the user initiation the request.
* @param apiRequestID the API request ID.
*/
case class PermissionsForProjectGetRequestADM(projectIri: IRI,
requestingUser: UserADM,
apiRequestID: UUID
) extends PermissionsResponderRequestADM {

implicit protected val stringFormatter: StringFormatter = StringFormatter.getInstanceForConstantOntologies
stringFormatter.validateProjectIri(projectIri, throw BadRequestException(s"Invalid project IRI"))

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
&& !requestingUser.permissions.isProjectAdmin(projectIri)) {
// not a system or project admin
throw ForbiddenException("Permissions can only be queried by system and project admin.")
}
}

// Administrative Permissions

/**
Expand All @@ -150,10 +174,8 @@ case class AdministrativePermissionsForProjectGetRequestADM(projectIri: IRI,

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
&& !requestingUser.permissions.isProjectAdmin(projectIri)
&& !requestingUser.isSystemUser
) {
// not a system admin
&& !requestingUser.permissions.isProjectAdmin(projectIri)) {
// not a system or project admin
throw ForbiddenException("Administrative permission can only be queried by system and project admin.")
}
}
Expand Down Expand Up @@ -322,10 +344,8 @@ case class DefaultObjectAccessPermissionsForProjectGetRequestADM(projectIri: IRI

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
&& !requestingUser.permissions.isProjectAdmin(projectIri)
&& !requestingUser.isSystemUser
) {
// not a system admin
&& !requestingUser.permissions.isProjectAdmin(projectIri)) {
// not a system or project admin
throw ForbiddenException("Default object access permissions can only be queried by system and project admin.")
}
}
Expand Down Expand Up @@ -537,6 +557,16 @@ case class DefaultObjectAccessPermissionCreateRequestADM(createRequest: CreateDe

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Responses
// All Permissions
/**
* Represents an answer to [[PermissionsForProjectForProjectGetRequestADM]].
*
* @param allPermissions the retrieved sequence of [[PermissionInfoADM]]
*/
case class PermissionsForProjectGetResponseADM(allPermissions: Set[PermissionInfoADM]
) extends KnoraResponseADM with PermissionsADMJsonProtocol {
def toJsValue = permissionsForProjectGetResponseADMFormat.write(this)
}

// Administrative Permissions

Expand Down Expand Up @@ -763,6 +793,16 @@ case class PermissionsDataADM(groupsPerProject: Map[IRI, Seq[IRI]] = Map.empty[I
}
}

/**
* Represents 'knora-base:AdministrativePermission'
*
* @param iri the IRI of the permission.
* @param permissionType the type of the permission.
*/
case class PermissionInfoADM(iri: IRI, permissionType: IRI) extends Jsonable with PermissionsADMJsonProtocol {

def toJsValue = permissionInfoADMFormat.write(this)
}

/**
* Represents 'knora-base:AdministrativePermission'
Expand Down Expand Up @@ -1022,6 +1062,7 @@ trait PermissionsADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtoc
// implicit val changeDefaultObjectAccessPermissionAPIRequestADMFormat: RootJsonFormat[ChangeDefaultObjectAccessPermissionAPIRequestADM] = jsonFormat(ChangeDefaultObjectAccessPermissionAPIRequestADM, "iri", "forProject", "forGroup", "forResourceClass", "forProperty", "hasPermissions")
implicit val permissionADMFormat: JsonFormat[PermissionADM] = jsonFormat(PermissionADM.apply, "name", "additionalInformation", "permissionCode")
// apply needed because we have an companion object of a case class
implicit val permissionInfoADMFormat: JsonFormat[PermissionInfoADM] = lazyFormat(jsonFormat(PermissionInfoADM, "iri", "permissionType"))
implicit val administrativePermissionADMFormat: JsonFormat[AdministrativePermissionADM] = lazyFormat(jsonFormat(AdministrativePermissionADM, "iri", "forProject", "forGroup", "hasPermissions"))
implicit val objectAccessPermissionADMFormat: JsonFormat[ObjectAccessPermissionADM] = jsonFormat(ObjectAccessPermissionADM, "forResource", "forValue", "hasPermissions")
implicit val defaultObjectAccessPermissionADMFormat: JsonFormat[DefaultObjectAccessPermissionADM] = lazyFormat(jsonFormat6(DefaultObjectAccessPermissionADM))
Expand All @@ -1034,4 +1075,5 @@ trait PermissionsADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtoc
implicit val defaultObjectAccessPermissionForIriGetResponseADMFormat: RootJsonFormat[DefaultObjectAccessPermissionForIriGetResponseADM] = jsonFormat(DefaultObjectAccessPermissionForIriGetResponseADM, "default_object_access_permission")
implicit val defaultObjectAccessPermissionForProjectGroupGetResponseADMFormat: RootJsonFormat[DefaultObjectAccessPermissionGetResponseADM] = jsonFormat(DefaultObjectAccessPermissionGetResponseADM, "default_object_access_permission")
implicit val defaultObjectAccessPermissionCreateResponseADMFormat: RootJsonFormat[DefaultObjectAccessPermissionCreateResponseADM] = jsonFormat(DefaultObjectAccessPermissionCreateResponseADM, "default_object_access_permission")
implicit val permissionsForProjectGetResponseADMFormat: RootJsonFormat[PermissionsForProjectGetResponseADM] = jsonFormat(PermissionsForProjectGetResponseADM, "permissions")
}
Expand Up @@ -71,6 +71,7 @@ class PermissionsResponderADM(responderData: ResponderData) extends Responder(re
case DefaultObjectAccessPermissionsStringForResourceClassGetADM(projectIri, resourceClassIri, targetUser, requestingUser) => defaultObjectAccessPermissionsStringForEntityGetADM(projectIri, resourceClassIri, None, ResourceEntityType, targetUser, requestingUser)
case DefaultObjectAccessPermissionsStringForPropertyGetADM(projectIri, resourceClassIri, propertyTypeIri, targetUser, requestingUser) => defaultObjectAccessPermissionsStringForEntityGetADM(projectIri, resourceClassIri, Some(propertyTypeIri), PropertyEntityType, targetUser, requestingUser)
case DefaultObjectAccessPermissionCreateRequestADM(createRequest, requestingUser, apiRequestID) => defaultObjectAccessPermissionCreateRequestADM(createRequest, requestingUser, apiRequestID)
case PermissionsForProjectGetRequestADM(projectIri, groupIri, requestingUser) => permissionsForProjectGetRequestADM(projectIri, groupIri, requestingUser)
case other => handleUnexpectedMessage(other, log, this.getClass.getName)
}

Expand Down Expand Up @@ -1263,6 +1264,47 @@ class PermissionsResponderADM(responderData: ResponderData) extends Responder(re
} yield taskResult
}

/**
* Gets all permissions defined inside a project.
*
* @param projectIRI the IRI of the project.
* @param requestingUser the [[UserADM]] of the requesting user.
* @param apiRequestID the API request ID.
* @return a list of of [[PermissionInfoADM]] objects.
*/
private def permissionsForProjectGetRequestADM(projectIRI: IRI,
requestingUser: UserADM,
apiRequestID: UUID
): Future[PermissionsForProjectGetResponseADM] = {

for {
sparqlQueryString <- Future(org.knora.webapi.messages.twirl.queries.sparql.admin.txt.getProjectPermissions(
triplestore = settings.triplestoreType,
projectIri = projectIRI
).toString())

permissionsQueryResponse <- (storeManager ? SparqlConstructRequest(sparqlQueryString)).mapTo[SparqlConstructResponse]

/* extract response statements */
permissionsQueryResponseStatements: Map[IRI, Seq[(IRI, String)]] = permissionsQueryResponse.statements


permissionsInfo: Set[PermissionInfoADM] = if (permissionsQueryResponseStatements.isEmpty) {
throw NotFoundException(s"No permission could be found for $projectIRI.")
} else {
permissionsQueryResponseStatements.map{ statement =>
val permissionIri = statement._1
val (_, permissionType) = statement._2.filter(_._1 == OntologyConstants.Rdf.Type).head
PermissionInfoADM(iri = permissionIri, permissionType = permissionType)
}.toSet
}

/* construct response object */
response = permissionsmessages.PermissionsForProjectGetResponseADM(permissionsInfo)

} yield response
}

}


0 comments on commit 3571dab

Please sign in to comment.