Skip to content
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

feat(projects)!: Change shortname to xsd:NCName forma, Escape special character in payloads of projects endpoints (DSP-1555 ) #1886

Merged
29 changes: 18 additions & 11 deletions docs/03-apis/api-admin/projects.md
Expand Up @@ -58,34 +58,40 @@ License along with DSP. If not, see <http://www.gnu.org/licenses/>.
### Create a new project:

- Required permission: SystemAdmin
- Required information: shortname (unique; used for named graphs),
status, selfjoin
- Optional information: longname, description, keywords, logo
- Required information:
- shortcode (unique, 4-digits)
- shortname (unique; it should be in the form of a
[xsd:NCNAME](https://www.w3.org/TR/xmlschema11-2/#NCName))
- description (collection of descriptions as strings with language tag.)
- keywords (collection of keywords)
- status (true, if project is active. false, if project is inactive)
- selfjoin
- Optional information: longname, logo
- Returns information about the newly created project
- Remark: There are two distinct use cases / payload combination:

(1) change ontology and data graph: ontologygraph, datagraph,

(2) basic project information: shortname, longname, description,
(2) basic project information: shortcode, shortname, longname, description,
keywords, logo, institution, status, selfjoin

- TypeScript Docs: projectFormats - CreateProjectApiRequestV1
- POST: `/admin/projects/`
- BODY:

```json
{
"shortname": "newproject",
"longname": "project longname",
"description": "project description",
"keywords": "keywords",
"description": [{"value": "project description", "language": "en"}],
"keywords": ["test project"],
"logo": "/fu/bar/baz.jpg",
"status": true,
"selfjoin": false
}
```

Additionally, each project can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below:
Additionally, each project can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form)
specified by the `id` in the request body as below:

```json
{
Expand All @@ -100,6 +106,7 @@ Additionally, each project can have an optional custom IRI (of [Knora IRI](../ap
"selfjoin": false
}
```

#### Default set of permissions for a new project:
When a new project is created, following default permissions are added to its admins and members:
- ProjectAdmin group receives an administrative permission to do all project level operations and to create resources
Expand All @@ -123,7 +130,7 @@ belongs to the project. This default object access permission is retrievable thr

- Required permission: SystemAdmin / ProjectAdmin
- Changeable information: shortname, longname, description,
keywords, logo, status, selfjoin
keywords, logo, status, selfjoin. The payload must at least contain a new value for one of these properties.
- TypeScript Docs: projectFormats - ChangeProjectApiRequestV1
- PUT: `/admin/projects/iri/<projectIri>`
- BODY:
Expand All @@ -132,8 +139,8 @@ belongs to the project. This default object access permission is retrievable thr
{
"shortname": "newproject",
"longname": "project longname",
"description": "project description",
"keywords": "keywords",
"description": [{"value": "a new description", "language": "en"}],
"keywords": ["a new key"],
"logo": "/fu/bar/baz.jpg",
"status": true,
"selfjoin": false
Expand Down
Expand Up @@ -885,14 +885,6 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
private val UsernameRegex: Regex =
"""^(?=.{4,50}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?<![_.])$""".r

/**
* A regex that matches a valid project shortname
* - 4 - 50 characters long
* - Only contains alphanumeric characters.
*/
private val ProjectShortnameRegex: Regex =
"""^(?=.{4,50}$)[a-zA-Z0-9]+$""".r

/**
* The information that is stored about non-Knora IRIs.
*/
Expand Down Expand Up @@ -2450,33 +2442,6 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
OntologyConstants.NamedGraphs.DataNamedGraphStart + "/" + project.shortcode + "/" + project.shortname + "/metadata"
}

/**
* Given the project IRI, checks if it is in a valid format.
*
* @param iri the project's IRI.
* @return the IRI of the project.
*/
def validateProjectIri(iri: IRI, errorFun: => Nothing): IRI = {
if (isKnoraProjectIriStr(iri)) {
iri
} else {
errorFun
}
}

/**
* Given the optional project IRI, checks if it is in a valid format.
*
* @param maybeIri the optional project's IRI to be checked.
* @return the same optional IRI.
*/
def validateOptionalProjectIri(maybeIri: Option[IRI], errorFun: => Nothing): Option[IRI] = {
maybeIri match {
case Some(iri) => Some(validateProjectIri(iri, errorFun))
case None => None
}
}

/**
* Check that the supplied IRI represents a valid project IRI.
*
Expand All @@ -2501,7 +2466,7 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
* project IRI.
* @return the same optional string but escaped.
*/
def validateAndEscapeOptionalProjectIri(maybeString: Option[String], errorFun: => Nothing): Option[String] = {
def validateAndEscapeOptionalProjectIri(maybeString: Option[String], errorFun: => Nothing): Option[IRI] = {
maybeString match {
case Some(s) => Some(validateAndEscapeProjectIri(s, errorFun))
case None => None
Expand All @@ -2517,7 +2482,7 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
* @return the same string.
*/
def validateAndEscapeProjectShortname(value: String, errorFun: => Nothing): String = {
ProjectShortnameRegex.findFirstIn(value) match {
NCNameRegex.findFirstIn(value) match {
case Some(shortname) => toSparqlEncodedString(shortname, errorFun)
case None => errorFun
}
Expand Down Expand Up @@ -2594,6 +2559,14 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
}
}

def escapeOptionalString(maybeString: Option[String], errorFun: => Nothing): Option[String] = {
maybeString match {
case Some(s) =>
Some(toSparqlEncodedString(s, errorFun))
case None => None
}
}

/**
* Given the list IRI, checks if it is in a valid format.
*
Expand Down Expand Up @@ -3263,4 +3236,10 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
StringLiteralV2(value = fromSparqlEncodedString(stringLiteral.value), language = stringLiteral.language))
)
}
def unescapeOptionalString(optionalString: Option[String]): Option[String] = {
optionalString match {
case Some(s: String) => Some(fromSparqlEncodedString(s))
case None => None
}
}
}
Expand Up @@ -54,13 +54,13 @@ case class CreateAdministrativePermissionAPIRequestADM(id: Option[IRI] = None,
def toJsValue: JsValue = createAdministrativePermissionAPIRequestADMFormat.write(this)

implicit protected val stringFormatter: StringFormatter = StringFormatter.getInstanceForConstantOntologies
stringFormatter.validateProjectIri(forProject, throw BadRequestException(s"Invalid project IRI"))
stringFormatter.validateAndEscapeProjectIri(forProject, throw BadRequestException(s"Invalid project IRI $forProject"))
stringFormatter.validateOptionalPermissionIri(
id,
throw BadRequestException(s"Invalid permission IRI ${id.get} is given."))
if (hasPermissions.isEmpty) throw BadRequestException("Permissions needs to be supplied.")
if (!OntologyConstants.KnoraAdmin.BuiltInGroups.contains(forGroup)) {
stringFormatter.validateGroupIri(forGroup, throw BadRequestException(s"Invalid group IRI ${forGroup}"))
stringFormatter.validateGroupIri(forGroup, throw BadRequestException(s"Invalid group IRI $forGroup"))
}
}

Expand All @@ -84,7 +84,7 @@ case class CreateDefaultObjectAccessPermissionAPIRequestADM(id: Option[IRI] = No
def toJsValue: JsValue = createDefaultObjectAccessPermissionAPIRequestADMFormat.write(this)

implicit protected val stringFormatter: StringFormatter = StringFormatter.getInstanceForConstantOntologies
stringFormatter.validateProjectIri(forProject, throw BadRequestException(s"Invalid project IRI"))
stringFormatter.validateAndEscapeProjectIri(forProject, throw BadRequestException(s"Invalid project IRI $forProject"))
stringFormatter.validateOptionalPermissionIri(
id,
throw BadRequestException(s"Invalid permission IRI ${id.get} is given."))
Expand Down Expand Up @@ -231,7 +231,7 @@ case class PermissionsForProjectGetRequestADM(projectIri: IRI,
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -345,7 +345,7 @@ case class AdministrativePermissionsForProjectGetRequestADM(projectIri: IRI,
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -386,7 +386,7 @@ case class AdministrativePermissionForProjectGroupGetADM(projectIri: IRI, groupI
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -488,7 +488,7 @@ case class DefaultObjectAccessPermissionsForProjectGetRequestADM(projectIri: IRI
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -516,7 +516,7 @@ case class DefaultObjectAccessPermissionGetRequestADM(projectIri: IRI,
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -592,7 +592,7 @@ case class DefaultObjectAccessPermissionsStringForResourceClassGetADM(projectIri
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down Expand Up @@ -628,7 +628,7 @@ case class DefaultObjectAccessPermissionsStringForPropertyGetADM(projectIri: IRI
extends PermissionsResponderRequestADM {

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

// Check user's permission for the operation
if (!requestingUser.isSystemAdmin
Expand Down