Search

Advanced Concepts

Find here an overview of advanced concepts.

Content

Security

With respect to web services, authentication is the act of proving the validity of a presented user claim passed with the request. This typically comprises verifying the request user’s identity, tenant, and additional claims like granted roles. Briefly, authentication controls who is using the service. In contrast, authorization makes sure that the user has the required privileges to access the requested resources. Hence, authorization is about controlling what the user is doing with the service.

Obviously, a proper authorization needs to rely on a processed authentication, which asserts the user claims. In addition, invalid requests have to be rejected as soon as possible by the server in order to keep the resource impact as low as possible and make denial of service attacks much more complex. Consequently, authentication needs to be one of the first steps in the process pipeline of a request. This is the reason why it’s not integral part of the CAP runtime and needs to be configured on application level, for example, by adding additional dependencies. Currently, the CAP Java Runtime supports XSUAA authentication out of the box, but custom authentication can be configured in a flexible manner. Find details about configuring authentication in section Authentication.
However, a comprehensive authorization service is fully implemented by the runtime and is available wither by following a declarative approach through the CDS model or by explicitly making usage of the Authorization API. All about authorization is summarized in section Authorization.

Authentication

As described before, the CAP Java runtime offers out-of-the-box support for the XSUAA authentication service on the SAP Cloud Platform, which is based on OAuth 2.0 protocol. Shortly, the web flow following the OAuth protocol involves three agents:

  • The resource owner (user)
  • The resource server (web service)
  • And the authentication server (XSUAA server).

As first step, the resource owner retrieves a signed access token from the resource server. This token contains all relevant user claims such as user identity, tenant identifier, and granted privileges. Requests to the resource server (web service) bear the token in the authorization header and gets validated before the request is being processed. On successful authentication, the user information is available in the scope of the request.

Spring Boot

To enable authentication with Spring Boot, add the following Maven dependency to the XSUAA library in the pom.xml file of your service:

<dependency>
	<groupId>com.sap.cloud.security.xsuaa</groupId>
	<artifactId>xsuaa-spring-boot-starter</artifactId>
	<version>${xsuaa.version}</version>
</dependency>

as well as the dependency to the XSUAA feature:

<dependency>
	<groupId>com.sap.cds</groupId>
	<artifactId>cds-feature-xsuaa</artifactId>
</dependency>

At runtime, the XSUAA library additionally requires a proper UAA service configuration, which on SAP Cloud Platform Cloud Foundry is accomplished binding a UAA service to your application. Find more details about UAA service configuration on SAP Cloud Platform below.

With the previously mentioned dependencies and a UAA service binding in place, the CAP Java runtime activates a Spring security configuration, which enforces XSUAA authentication for all nonpublic endpoints (according to your CDS model) of CAP services. As a result, these services can’t be invoked without sending a valid token with the user request. There are several application configuration properties to control the default behavior of the security configuration:

  • cds.security.openUnrestrictedEndpoints = false switches off automatic configuration of public CDS service endpoints.
  • cds.security.xsuaa.enabled = false switches off XSUAA security configuration at all.

The CAP Java Runtime configures authentication for the exposed service endpoints according to the CDS model. Note, that unrestricted service endpoints are accessible for anonymous users!

Maintaining Authentication Manually

If you want to overwrite the automatic security configuration for specific endpoints of your application – for instance if they require an alternative authentication method or should be public – you can add an additional Spring security configuration with a higher priority as done in the following example:

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
@Order(1) // needs to be evaluated before the standard security configuration
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .requestMatchers().antMatchers("/public/**").and()
      .csrf().disable() // don't insist on csrf tokens in put, post etc.
    .authorizeRequests()
      .anyRequest().permitAll();
  }
}

Here all URLs matching /public/** are opened for public access.

Be cautious with the configuration of the HttpSecurity instance in your custom code. For instance:

http.authorizeRequests().antMatchers("/public/**").permitAll()

will overwrite all endpoints of the application, fully overruling the CAP endpoints in contrast to:

AppSecurityConfig.configure()

Other frequent examples are Spring actuators that come with own endpoints and that might need custom authentication methods or at least custom authorization. For example, to apply basic authentication to such endpoints, have a look at the following example:

@Configuration
@EnableWebSecurity
@Order(1)
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
      .requestMatchers().antMatchers("/actuator/**").and()
          .httpBasic().and()
      .authorizeRequests().anyRequest().authenticated();
    }
	
    @Override
    public void configure(AuthenticationManagerBuilder auth)
      throws Exception {
      // -> configure basic authentication here with PasswordEncoder etc.
    }
}

Note, that you have to add the actual configuration of basic authentication.

Plain Java and SAP Java Buildpack

If your application uses plain Java and is deployed as war archive on SAP Cloud Platform with the SAP Java Buildpack, you need to add the Maven dependency to XSUAA in the following way:

<dependency>
	<groupId>com.sap.cloud.security.xsuaa</groupId>
	<artifactId>api</artifactId>
	<version>${xsuaa.version}</version>
	<scope>provided</scope>
</dependency>

Note that the dependency scope is provided here as the SAP Java Buildpack adds the required XSUAA library at deployment time.

Additional configuration is needed to activate XSUAA authentication in the web.xmlfile for the Tomcat server as shown in the following snippet:

<web-app>
<display-name>sample</display-name>
  <login-config>
    <auth-method>XSUAA</auth-method>
  </login-config>
</web-app>

See section XSUAA sample application for SAP Java Buildpack for more details.

UI Flows

HTTP endpoints with activated OAuth-based authentication require a bearer token in each request. As explained before, resource owners need to fetch a valid token from the authorization server. For UI-based flows, the token retrieval is typically handled by a UI frontend application, which redirects HTTP requests, that miss an access token, to a dedicated logon page. The frontend can cache the token and append it in subsequent request being forwarded to the backend service.

This approach is shown in the Application Router example for XSUAA.

Authorization

Authorization is handled by the CAP Java Runtime. By using declarative authorization annotations in your CDS model, the CAP Java Runtime can configure authorization of the affected endpoints automatically. We distinguish between two different authorization methods:

  • Role-based authorization means, that the exposed part of the API depends on the roles, which are assigned to the request user.
  • Instance-based authorization allows to define user privileges even on entity element level, that means a user can only access elements of an entity that fulfill a certain condition.

A precise description of the general authorization concept in CAP can be found in section Authorization.

Role-Based Authorization

You can use the CDS annotation @requires at a CDS service to specify which role a user needs to access the service in general (or, more specifically, to send an event to the service). In addition, use the annotation @restrict on service entity level to specify the role needed to send specific events. Actions and functions can be restricted to certain roles with @requires, respectively.

@restrict and @requires annotations defined on different model levels are evaluated hierarchically (logical AND). For instance, if a user has no access to a service, this also implies that the entities, using actions and functions of the service is forbidden as well. However, several grants in a @restrict annotation of a single entity are logically combined by OR.

The following simple example demonstrates how to model role-based and instance-based authorization. It contains a single DB entity db.books, which is exposed to four different types of users. The access rules for the different users are:

  • Authenticated users can only read columns title, publisher, and price of all books.
  • Users with role vendor have full read access to all books and can also edit books, which have a publisher they’re assigned to.
  • accountant users should be able to read books and trigger accounting action on them.
  • admin users have unrestricted access to all books, but they can’t trigger accounting.
context db {
  entity Books : cuid, managed {
	 title     : localized String(111);
	 publisher : String(111);
	 stock     : Integer;
	 price     : Decimal(9,2);
  }
}

@path:'browse'
service CatalogService @(requires: 'authenticated-user') {
  entity Books @(restrict: [{ grant: 'READ' } ]) 
  as SELECT from db.Books { title, publisher, price };
};

@path:'internal'
service EditService @(requires: ['vendor', 'accountant']) {
  entity Books @(restrict: [
    { grant: 'READ' },
    { grant: 'WRITE', to: 'vendor', where: '$user.publishers = publisher' } ]) 
  as projection on db.Books;
   
  action doAccounting @(requires:'accountant') ();
};

@path:'admin'
service AdminService @(requires: 'admin') {
  entity Books as projection on db.Books;
};

The table summarizes the privileges of the different users as enforced by the @restrict and @requires annotations in the model:

Role READ WRITE doAccounting
Authenticated user (role not relevant) YES (/browse) NO NO
Vendor YES (/internal or /browse) YES (/internal, but only books with matching published) NO
Accountant YES (/internal or /browse) NO YES (/internal)
Admin YES (/browse or /admin) YES (/admin) NO

Keep in mind that users can have several roles assigned at the same time.

In the current version of CAP Java Runtime, the security annotations are only evaluated on the target entity of the request. Restrictions on second-level entities touched by the query aren’t checked.

This has the following implications:

  • Restrictions of (recursively) expanded or inlined entities of a READ request aren’t regarded.
  • Deep inserts and updates are also only checked on the target entity.

To prevent exposing data of second-level entities through an accessible service entity, minimize the number of exposed associations or compositions in the service entity and add adequate custom handlers to close remaining gaps.

Additional remarks and tips:

  • Don’t forget to restrict auto-exposed entities, which might be open for public access otherwise.
  • WRITE is an accepted abbreviation in the grant clause that comprises all standard CDS events with write semantic: CREATE, UPDATE, UPSERT, and DELETE.

Instance-Based Authorization

Whereas role-based authorization applies to whole entities, only, instance-based authorization allows fine-grained declaration a user is allowed to access within an entity based on his assigned properties. This is also demonstrated in the previous example. vendor users are only allowed to change the entity books, which have a publisher listed in the user’s publisher attribute ($user.publisher). User attributes are presented in the security token sent with the request. Details about the general concept of instance-based authorization can be found in Instance-based access.

Please note, that generally a user attribute referred to with $user.<attribute-name> is interpreted as an array of strings and not single value. (Sub-)Expressions in the where-condition with $user attributes are evaluated element for each user attribute value individually and evaluate to true in case one of the values matches. In the current version, an empty attribute list indicates unrestricted access.

The CAP Java Runtime translates the where-condition in the @restrict annotation to a CQN predicate, which is appended to the CQN statement of the request in an early BEFORE handler. This applies only to READ,UPDATE, and DELETE events.

In the current version of CAP Java Runtime, the following limitations apply:

  • For UPDATE and DELETE events no paths in the where-condition are supported.
  • Paths in where-conditions with to-many associations or compositions aren’t supported.
  • Input data that comes with CREATE, UPDATE or UPSERT isn’t checked against the where-condition. If necessary, custom handlers can cover this in a BEFORE handler.

Authorization and Drafts

The draft flow has some specific implications on the authorization logic that differs from requests to none-draft entities:

  • Draft entities can only be edited by the user who has created it.
  • In general, a grant for a standard CDS event comprises the grants for corresponding draft events. For instance, CREATE implies DRAFT_NEW. Hence, draft events don’t need to be mentioned in @restrict.
  • Instance-based conditions don’t apply for draft entities. But only entities that meet the condition can be activated or put into draft mode.

Create XSUAA Configuration

Compile your CDS model with authentication annotations into a full xs-security.json. For this, run:

mkdir gen
cds compile srv/ --to xsuaa,json > gen/xs-security.json

Note how in xs-security.json the different CDS roles CDS model has been mapped to XSUAA scopes and role template. It’s recommended to add this compile step to your build.

To manually create an XSUAA service with name cds-service-uaa based on this configuration and bind it to the application cds-application, run:

cf create-service xsuaa application cds-service-uaa -c gen/xs-security.json
cf bind-service cds-application cds-service-uaa

Both manual steps aren’t necessary when you model the respective XSUAA service and its dependencies in a deployment manifest before pushing to the cloud.

Set Up the Roles for the Application in the Cloud Cockpit

Once you’ve deployed your application to SAP Cloud Platform, you need set up the roles and role collections in the SAP Cloud Cockpit and assign them to users. This adds the necessary authorization information to the access token when the user logs on to your application through the XSUAA and Application Router.

Following configuration steps need to be done in the SAP Cloud Platform Cockpit within your Subaccount:

  1. Add the roles to role collections

    Navigate to Security > Role Collections. Choose New Role Collection, enter a name, and confirm. Now, click on the new role collection and choose Add Role. Choose the application, role template, and role to finish the creation step.

    In case you added a where clause in the @restrict annotation in the CDS model (which is currently not supported by the runtime), it might be necessary to create a role with corresponding attributes in advance. See section Building Roles and Role Collections for Applications for more details in SAP Cloud Platform documentation.

  2. Assign the role collections to users

    The user role assignment is done in Security > Trust Configuration. Select an IDP and add the assignment to role collection to specific users. See section Assign Role Collections for more details in SAP Cloud Platform documentation..

Mock Users

If security is activated in Spring as described before, the CAP Java Runtime creates a security configuration, which accepts mock users for testing purposes. The mock user configuration only applies if:

  • The service runs without an XSUAA service binding (none-productive mode)
  • Mock users are defined

You can define mock users in a Spring profile, which should be only active during testing as in the following example:

---
spring:
  profiles: testing
cds:
  security:
    mock:
      users:
        - name: authenticated-user
          password: pass1
        - name: viewer-user
          password: pass2
          roles: 
            - viewer  
	  tenant: my-tenant

The first user with name authenticate-user has the ID 0815 and password pass1. No roles are granted. In contrast, user viewer-user has role viewer and tenant ID my-tenant. All users can log in using basic authentication.

In addition, Spring property cds.security.mock.enabled = false switches off any mock user configuration.

Data Protection and Privacy

Currently, there’s no out-of-the-box support for managing personal data. From the perspective of applications build on CAP, the CAP Java Runtime acts like a container that stores and serves data as defined in the CDS model. Hence, applications need to add required functionality - for example, by means of handlers - to fulfill general requirements with regards to personal data. Data stored by the Persistence Service is protected from unauthorized access - provided adequate access restrictions are declared in the CDS Model as already described.

Using CDS QL with a Static CDS Model

Static Model in the Query Builder

The Query Builder API allows you to dynamically create CDS QL queries using entity and element names given as strings:

Select.from("my.bookshop.Books")
  .columns("title")
  .where(book -> book.to("author").get("name").eq("Edgar Allan Poe"));

This query is constructed dynamically. It’s checked only at runtime that the entity my.bookshop.Authors actually exists and that it has the element name. Moreover, the developer of the query doesn’t get any code completion at design time. These disadvantages are avoided by using a static model to construct the query.

Model Interfaces

The static model is a set of interfaces that reflects the structure of the CDS model in Java (like element references with their types, associations, etc.) and allow to fluently build queries in a type-safe way. For every entity in the model, the model contains a corresponding StructuredType interface, which represents this type. As an example, for this CDS model:

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String(111);
  author : Association to Authors;
}

entity Authors {
  key ID : Integer;
  name   : String(111);
  books  : Association to many Books on books.author = $self;
}

the following model interfaces are generated:

@CdsName("my.bookshop.Books")
public interface Books_ extends StructuredType<Books_> {
  ElementRef<Integer> ID();
  ElementRef<String> title();
  Authors_ author();
  Authors_ author(Function<Authors_, Predicate> filter);
}
@CdsName("my.bookshop.Authors")
public interface Authors_ extends StructuredType<Authors_> {
  ElementRef<Integer> ID();
  ElementRef<String> name();
  Books_ books();
  Books_ books(Function<Books_, Predicate> filter);
}

Accessor Interfaces

The corresponding Data is captured in a Data Model similar to Java Beans. These beans are interfaces generated by the framework and providing the data access methods - getters and setters, and containing the CDS element names as well. The instances of the data model are created by the CDS QL Execution Engine (see example bellow).

Note the following naming convention: the model interfaces, which represent the structure of the CDS Model, always end with underscore, for example Books_. The accessor interface, which refers to Data Model, is simply the name of the CDS Entity - Books.

The following Data Model Interface is generated for Books:

@CdsName("my.bookshop.Books")
public interface Books extends CdsData {

  String ID = "ID";
  String TITLE = "title";
  String AUTHOR = "author";

  Integer getID();
  void setID(Integer id);
  
  String getTitle();
  void setTitle(String title);

  Authors getAuthor();
  void setAuthor(Map<String, ?> author);
}

Usage

In the query builder, the interfaces reference entities. The interface methods can be used in lambda expressions to reference elements or to compose path expressions:

Select<Books_> query = Select.from(Books_.class)			// Note the usage of model interface Books_ here
  .columns(book -> book.title())
  .where  (book -> book.author().name().eq("Edgar Allan Poe"));
  
List<Books> books = dataStore.execute(query).listOf(Books.class);	// After executing the query the result can be converted to a typed representation List of Books.

Generate the Static Model

The static model and accessor interfaces can be generated using the CDS4j Maven Plugin.

Setup

Configure the plugin in the <plugins> section of the pom.xml file:

<build>
  <plugins>
    <plugin>
      <groupId>com.sap.cds</groupId>
      <artifactId>cds4j-maven-plugin</artifactId>
      <version>${cds4j.version}</version>
      <configuration>
        <outputDirectory>${project.basedir}/src/gen</outputDirectory>
      </configuration>
      <executions>
        <execution>
          <id>cds4j-generate-model</id>
          <phase>generate-resources</phase>
          <goals>
            <goal>generate</goal>
          </goals>
          <configuration>
            <csnFile>${project.basedir}/src/main/resources/model.json</csnFile>
            <basePackage>my.model</basePackage>
            <eventContext>true</eventContext>
            <methodStyle>BEAN/FLUENT</methodStyle>
            <excludes>
              <exclude>my.bookshop.*</exclude>
              <exclude>my</exclude>
            </excludes>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Configuration

Below is a list of configuration options that the CDS4j Maven plugin accepts:

  • csnFile - path to the CSN file (required option).
  • outputDirectory - output directory for generated JPA entities (default is src/gen).
  • excludes- namespaces of definitions to be excluded from metadata generation.

    In the Setup example, the exclude my.bookshop.* will exclude all entities with namespace my.bookshop and below. The second exclude my will exclude only entities with the exact namespace my.

  • basePackage - a prefix package name for all generated interface packages.
  • eventContext - a boolean value to determine whether to generate interfaces extending EventContext for actions and functions.
  • methodStyle - method styling for accessor interface methods; follows either the Java Bean style method naming for getters & setters denoted by BEAN or the Fluent style denoted by FLUENT.
//BEAN
@CdsName("my.bookshop.User")
public interface User extends Row {
  String NAME = "name";

  String getName();

  void setName(String id);
}

//FLUENT
@CdsName("my.bookshop.User")
public interface User extends Row {
  String NAME = "name";

  String name();

  User name(String id);
}

Usage Without POM

The plugin can also be used without a Maven project that is, without a pom.xml file.

The following example generates the static model from the CSN file model.json into the folder out:

mvn com.sap.cds:cds4j-maven-plugin:1.2.1:generate -DcsnFile=model.json -DoutputDirectory=out

WARNING: Currently, the generator doesn’t support using Java reserved Keywords as identifiers in the CDS model.