cancel
Showing results for 
Search instead for 
Did you mean: 

More info on CDS 'requires' statement (Cloud Application Programming Model CAPM)

MikeDoyle
Active Contributor

In the Cloud Application Programming Model (CAPM) Help there is a tantalising glimpse of how to control access to a service by role (in Using Single-Purposed Services😞

 service AdminService @(requires:'admin')

I need to control access to services by Cloud Foundry role collection. Can I do that in the same way as above? Do I just put the name of the role collection in there (in place of 'admin')? Can I put a scope in there instead?

I would be very grateful if someone could share some documentation or examples that show what is supported. There doesn't seem to be anything like that in the documentation, even under Annotations.

Thanks!

cc christian.georgi daniel.hutzel

Accepted Solutions (1)

Accepted Solutions (1)

Daniel7
Advisor
Advisor

Perfect answer philip.mugglestone

Documentation, and with it official release of the feature, is on its way (in new format)... → here's an (unofficial) sneak preview of the respective Guide as available internally:

---------

Restrictions

Restrict access to services by adding @requires or @restrict annotations to services, entities or functions as in this example:

service ReviewsService @(requires: 'identified-user'){
  /*...*/
} 

service CustomerService @(requires: 'authenticated-user'){
  entity Orders @(restrict: [ 
    { grant: ['READ','WRITE'], to: 'admin' },
    { grant: 'READ', where: 'buyer = $user' },
  ]){/*...*/}
  entity Approval @(restrict: [
    { grant: 'WRITE', where: '$user.level > 2' } 
  ]){/*...*/}
}

The annotations are:

  • @restrict: allows fine-grained control through an array of privileges given as records
  • @requires: allows specifying one or more user roles (as a single string or an array of strings) the current user must be assigned.

Remark: Essentially @requires: is just a convenience shortcut, for example:

@requires: 'identified-user'

… is equivalent to:

@restrict: [{ grant:'*', to: 'identified-user' }]

… just more concise and more comprehensible.

Privileges

… are granted within @restrict: annotations and support these properties:

  • grant: one or more operations (as a string or an array of strings)
  • to: (optional) one or more user roles the privilege is granted to
  • where: (optional) a condition that further restricts access

Within where: conditions, use names prefixed with $ to refer to request attributes or attributes in the JWT token. Examples: $user, $user.foo or $foo.

Within where: conditions CQL syntax is allowed. Initially and and or is supported (no subselects).

It is possible to use where: and to: together in one grant: clause.

References to entity elements in where: conditions establish so-called instance-based authorizations. The filter conditions usually relate attributes of protected entities to user or request attributes.

User Roles

… can be freely chosen and are local to the current application. For example, in the above sample, the role name admin reflects the assumed role of bookshop administrators.

Note: While roles are actually mapped to OAuth scopes if OAuth is used, CDS-based Authorization deliberately refrains from using technical concepts in favour of user roles which are closer to the conceptual domain of business applications. This also results in much smaller JWT tokens.

Pseudo roles

The following are pre-defined pseudo roles:

  • authenticated-user refers to users identified by login
  • identified-user is identified by weaker mechanisms such as cookies
  • system-user is for client systems calling in with unnamed, technical users
  • any refers to all users including non-identified ones

identified-users is a superset of authenticated-users.
any is a superset of all others.

Enforcement

The service provider frameworks automatically enforce restrictions in generic handlers. They do so by evaluating the annotations in the CDS models and depending on the operations…

  • rejecting incoming requests if the conditions are not met in case of static authorizations
  • adding corresponding filters to the queries’s where clauses in case of read queries and instance-based authorizations

Alternatively, you can programmatically enforce restrictions in custom request handlers using this methods in you event handlers:

Enforcement API

Following is the API you can use for implementing your enforcement (given here in Node.js APIs; swift has corresponding ones) — the same are used by the generic enforcement:

  • req.user – sortcut for req.user.id in queries
  • req.user.id – access the current user’s unique ID, an arbitrary string
  • req.user.is(<role>) – check whether the user has assigned the given role
  • req.user.<attr> – access user-related attributes from JWT tokens
  • req.reject(403, ...) – reject a request due to missing authorizations
  • req.query.where(...) – to add a filter to the query’s where clause

Note: The function req.user.is(<role>) accepts role names introduced in the current application’s service models.

MikeDoyle
Active Contributor
0 Kudos

Thanks so much Daniel, the documentation looks really good. Will this work with custom handlers (i.e. with the cds.persistence.skip annotation)? I note that the documentation you shared says:

Alternatively, you can programmatically enforce restrictions in custom request handlers

I'm using a Java app with custom handlers which call S/4HANA Cloud using the SDK. I'm thinking that the annotations in my-service.xml won't do anything in my case? Or perhaps the 'service-level' requires statement will still work? I note that the before- and after-operation hooks, for example, aren't called with custom handlers.

If that's the case do you have any references of how to implement the checks in Java rather than node?

Ben
Participant
0 Kudos

Hi daniel.hutzel philip.mugglestone

Thank you very much for the sneak preview of this new functionality in Node.js handlers! I have been working with req.user.is(<role>) and req.user.<attr> and it is very handy!

When I was debugging a request handler, I came across the function

req.user.has( <roles>? ) 

and I tried to execute this function but always get error message: roles.some is not a function..

Could you please clarify what this function can be used for and how it is called?

I am also looking forward for official release of the new CAPM documentation. I hope this will happen soon.. 🙂

Best regards,

Ben

Answers (2)

Answers (2)

pmugglestone
Product and Topic Expert
Product and Topic Expert

Hi Mike,

Try scope name but without the $XSAPPNAME. prefix.

For example, with these scopes (as defined in xs-security.json):

	"scopes": [{
		"name": "$XSAPPNAME.User",
		"description": "User"
	}, {
		"name": "$XSAPPNAME.Admin",
		"description": "Administrator"
	}],

This should work:

service AdminService @(requires:'Admin')

service CatalogService @(requires:'User')

You'll still need to define role-templates and role collections etc. and assign the role collections to users just like in any Cloud Foundry app.

Hope that helps.

Philip

MikeDoyle
Active Contributor
0 Kudos

Thanks so much Philip, I will give that a try

joachimvanpraet
Active Participant

Hi benkrencker ,

The "some" function is a javascript function on Array.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some

When you use has, you have to provide an array of scope values. If there is one scope assigned to the current user, has returns true.

req.user.has(["user","admin"]) === req.user.is("user") || req.user.is("admin")

I've added some code to test this to my CAPM authorisation example.

https://github.com/jowavp/SAP-CAPM-Nodejs-Authorisation-example/blob/master/srv/service.js

kr,

Joachim