Package oracle.jdbc

Class EndUserSecurityContext

java.lang.Object
oracle.jdbc.EndUserSecurityContext

public final class EndUserSecurityContext extends Object

The security context in which the end user of an application executes database operations. An EndUserSecurityContext allows an Oracle JDBC Connection to operate under the security policy of a specific end user, rather than the security policy of a database user. This class is designed for integration with the Oracle Deep Data Security feature of Oracle Database (i.e., DeepSec).

Instances of EndUserSecurityContext encapsulate the set of values listed below.

Database Access Token
An EndUserSecurityContext must have a database access token. The claims of this token should be mapped to a DATA ROLE or APPLICATION IDENTITY defined in the database.
End User Token or End User Name
An EndUserSecurityContext must either have an end user token or end user name. The claims of an end user token should be mapped to a DATA ROLE defined in the database. An end user name should be defined as an END USER in the database.
Application Managed Data Roles
An EndUserSecurityContext may have data roles that are enabled and disabled based on application logic, rather than a database security policy. This type of DATA ROLE is defined in the database with a DISABLED clause.
Attributes
An EndUserSecurityContext may have attributes. Attributes are contained in JSON objects conforming to a JSON schema of an END USER CONTEXT declared in the database. Usage of attributes can be seen in this code example.
Look-Up Key
Every EndUserSecurityContext has a look-up key which the database associates to a set of END USER CONTEXT attributes. The database applies the same set of attributes to all database operations executed with the same look-up key. A user-defined look-up key may be provided when the createWithName(CharSequence, String, CharSequence) method is used to identify an end user by a name. Otherwise, a look-up key will be automatically generated when an end user is identified by a token, or when no look-up key is provided for an end user identified by a name. An automatically generated look-up key is derived from the database access token, the token or name of the end user, and application managed data roles. The effect of look-up keys can be seen in this code example.

API Contract

Instances of EndUserSecurityContext have the following API contract.
  • Instances of EndUserSecurityContext are immutable. Mutation is accomplished through "wither" methods, such as withDatabaseAccessToken(CharSequence). These methods return a copy of an EndUserSecurityContext with an old value replaced by a new value.
  • No method ever returns null.
  • No method accepts null. All methods throw a NullPointerException if a parameter is null or the parameter is a collection or array that contains null.
  • Methods do not retain references to any mutable objects provided as parameters or returned as results. For example, if a CharSequence is passed to a method, no reference to the original object will be kept, since a CharSequence may be mutable. Likewise, no reference will be retained to a char[] returned by a method, because a char[] is mutable. This behavior ensures that all instances of EndUserSecurityContext are immutable, while also allowing security-sensitive values to be cleared from memory, as shown in this code example.

Usage

A Connection from the Oracle JDBC Driver may be configured with an instance of EndUserSecurityContext in either of the following ways:

  • Application code may call setEndUserSecurityContext and clearEndUserSecurityContext as shown in the code example below.
  • An implementation of EndUserSecurityContextProvider can be installed as a service provider. Before each database operation, an OracleConnection retrieves the current EndUserSecurityContext from that provider. Open-source provider implementations are available from the ojdbc-extensions project. If setEndUserSecurityContext is called when a provider is also installed, Oracle JDBC ignores the provider until clearEndUserSecurityContext is called.

Code Examples

This first example shows how to use an EndUserSecurityContext with a database access token and end user token. In this example, the application defines a getDatabaseAccessToken() method that returns a token authorizing the application to access the database. An OAUTH client library might be used to request a database access token. The application also defines a getCurrentUser() method that returns information about the currently authenticated end user. This method might be implemented using a security library that provides authentication details to a thread when handling the end user's request.


  public void example(DataSource dataSource) throws SQLException {
    try (Connection connection = dataSource.getConnection()) {
 
      char[] databaseAccessToken = getDatabaseAccessToken();
      char[] endUserToken = getCurrentUser().getSecurityToken();
      final EndUserSecurityContext endUserSecurityContext;
      try {
        endUserSecurityContext = EndUserSecurityContext.createWithToken(
          CharBuffer.wrap(databaseAccessToken),
          CharBuffer.wrap(endUserToken));
      }
      finally {
        Arrays.fill(databaseAccessToken, (char)0);
        Arrays.fill(endUserToken, (char)0);
      }
 
      OracleConnection oracleConnection =
        connection instanceof OracleConnection
          ? (OracleConnection)connection
          : connection.unwrap(OracleConnection.class);
      oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
      try {
        executeSqlAsEndUser(connection);
      }
      finally {
         oracleConnection.clearEndUserSecurityContext();
      }
    }
  }
  

The next example shows how to check the expiration time of a database access token and end user token. In this example, the expiration times are checked to ensure the tokens won't expire in the next two minutes. The database only requires that tokens be valid at the start of a SQL operation, and so a two-minute threshold would be excessive if only one SQL command needs to be executed with the EndUserSecurityContext. But if multiple SQL commands are executed, the tokens will need to remain valid until the last SQL command begins executing. Two minutes is just an example of a threshold that might be used. The actual threshold should depend on application specific timeouts for JDBC Connection usage, such as those that a connection pool might offer.


  void example(EndUserSecurityContext endUserSecurityContext, Connection connection) throws SQLException {
     OffsetDateTime deadline = OffsetDateTime.now().plusMinutes(2);
 
     OffsetDateTime databaseAccessTokenExpiration = endUserSecurityContext.databaseAccessTokenExpiration();
     if (databaseAccessTokenExpiration.isBefore(deadline)) {
       String refreshedToken = refreshDatabaseAccessToken();
       endUserSecurityContext = endUserSecurityContext.withDatabaseAccessToken(refreshedToken);
     }
 
     OffsetDateTime endUserTokenExpiration = endUserSecurityContext.endUserTokenExpiration();
     if (endUserTokenExpiration.isBefore(deadline)) {
       String refreshedToken = refreshEndUserToken();
       endUserSecurityContext = endUserSecurityContext.withEndUserToken(refreshedToken);
     }
 
    OracleConnection oracleConnection =
      connection instanceof OracleConnection
        ? (OracleConnection)connection
        : connection.unwrap(OracleConnection.class);
 
    oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
    try {
      executeSqlAsEndUser(connection);
    }
    finally {
      oracleConnection.clearEndUserSecurityContext();
    }
  }
  

The next example shows how to configure an EndUserSecurityContext with attributes of two END USER CONTEXT objects defined as:

    CREATE END USER CONTEXT user_info USING JSON SCHEMA '{
      "type": "object",
      "properties": {
        "first_name": { "type": "string", "default": "(GUEST)" },
        "last_name": { "type": "string", "default": "(GUEST)" }
      }
    }';
 
    CREATE END USER CONTEXT location_info USING JSON SCHEMA '{
      "type" : "object",
      "properties" : {
        "address" : {
          "type" : "object",
          "properties" : {
            "street_name": {"type": "string", "default": "(UNKNOWN)"},
            "address_number": {"type": "integer", "default": 0}
          }
        },
        "country": { "type": "string", "default": "(UNKNOWN)"},
        "state": { "type": "string", "default": "(UNKNOWN)"},
        "city": { "type": "string", "default": "(UNKNOWN)"}
      }
    }';
  

While the attributes of an END USER CONTEXT are represented as a JSON object, an application might use a different representation to store information about its end users. In the code that follows, an application defines a UserDetails object that encapsulates attributes of its end users, and it translates this information into JSON objects. The JSON objects are represented as instances of OracleJsonObject created by an OracleJsonFactory.

It is important to note that a fully-qualified END USER CONTEXT name must be provided in the Map passed to the withAttributes method. A fully-qualified name includes the name of the database schema in which an END USER CONTEXT is declared, using this format: {schema}.{name}. The database will not recognize unqualified names of an END USER CONTEXT.


  void example(
    UserDetails userDetails,
    EndUserSecurityContext endUserSecurityContext,
    Connection connection)
    throws SQLException {
 
    OracleJsonFactory jsonFactory = new OracleJsonFactory();
 
    OracleJsonObject jsonUserInfo = jsonFactory.createObject();
    Name name = userDetails.name();
    jsonUserInfo.put("first_name", name.firstName());
    jsonUserInfo.put("last_name", name.lastName());
 
    OracleJsonObject jsonLocationInfo = jsonFactory.createObject();
    Location location = userDetails.location();
    jsonLocationInfo.put("city", location.city());
    jsonLocationInfo.put("state", location.state());
    jsonLocationInfo.put("country", location.country());
 
    OracleJsonObject jsonAddress = jsonFactory.createObject();
    jsonAddress.put("street_name", location.streetName());
    jsonAddress.put("address_number", location.addressNumber());
    jsonLocationInfo.put("address", jsonAddress);
 
    // END USER CONTEXT names MUST be fully qualified with their schema name
    endUserSecurityContext = endUserSecurityContext.withAttributes(Map.of(
      "app_schema.user_info", jsonUserInfo,
      "app_schema.location_info", jsonLocationInfo));
 
    OracleConnection oracleConnection =
      connection instanceof OracleConnection
        ? (OracleConnection)connection
        : connection.unwrap(OracleConnection.class);
 
    oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
    try {
      executeSqlAsEndUser(connection);
    }
    finally {
      oracleConnection.clearEndUserSecurityContext();
    }
  }
  

The next example shows how the database stores attributes based on a look-up key that is automatically generated from the database access token, end user token, and application managed data roles of an EndUserSecurityContext.


  void example(EndUserSecurityContext endUserSecurityContext, Connection connection) throws SQLException {
    OracleConnection oracleConnection =
      connection instanceof OracleConnection
        ? (OracleConnection) connection
        : connection.unwrap(OracleConnection.class);
    try {
      // Executes SQL with: {"a":1, "b":1}
      OracleJsonObject attributes = new OracleJsonFactory().createObject();
      attributes.put("a", 1);
      attributes.put("b", 1);
      endUserSecurityContext = endUserSecurityContext.withAttributes("scott.example", attributes);
      oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
      executeSqlAsEndUser(connection);
 
      // Executes SQL with: {"a":1, "b":2}, even though "a" is not present in
      // the attributes of the EndUserSecurityContext. The value of "a" is 1
      // because the look-up key is identical to that of the previous
      // operation. The look-up key is identical because the database
      // access token, end user token, and application managed data roles are
      // identical.
      attributes.remove("a");
      attributes.put("b", 2);
      endUserSecurityContext = endUserSecurityContext.withAttributes("scott.example", attributes);
      oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
      executeSqlAsEndUser(connection);
 
      // Executes SQL with: {"a":1, "b":2}, even though no attributes are
      // present in the EndUserSecurityContext, the database will again see
      // an identical look-up key.
      endUserSecurityContext = endUserSecurityContext.withAttributes(Collections.emptyMap());
      oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
      executeSqlAsEndUser(connection);
    }
    finally {
      oracleConnection.clearEndUserSecurityContext();
    }
  }
  
Since:
26
  • Method Details

    • databaseAccessTokenExpiration

      public OffsetDateTime databaseAccessTokenExpiration()

      Returns the expiration time of the database access token. This method can be used to check if the database access token needs to be refreshed before a long-running sequence of SQL operations, as shown in this example.

      Returns:
      The expiration time of the database access token. Not null.
    • databaseAccessToken

      public char[] databaseAccessToken()

      Returns the database access token of this EndUserSecurityContext.

      Returns:
      The database access token. Not null. Not retained.
    • withDatabaseAccessToken

      public EndUserSecurityContext withDatabaseAccessToken(CharSequence databaseAccessToken)

      Returns a copy of this EndUserSecurityContext with a new database access token.

      Parameters:
      databaseAccessToken - A database access token. Not null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new database access token. Not null.
    • endUserTokenExpiration

      public OffsetDateTime endUserTokenExpiration()

      Returns the expiration time of the end user token, or OffsetDateTime.MAX if the end user is identified by a name instead of a token. This method can be used to check if the end user token needs to be refreshed before a long-running sequence of SQL operations, as shown in this example.

      Returns:
      The expiration time of the end user token, or OffsetDateTime.MAX if the end user is identified by a name instead of a token. Not null.
    • endUserToken

      public Optional<char[]> endUserToken()

      Returns an Optional containing the end user token, or Optional.empty() if the end user is identified by a name instead of a token.

      Returns:
      An Optional containing an end user token, or Optional.empty(). Not null. The char[] of a non-empty Optional is not retained.
    • withEndUserToken

      public EndUserSecurityContext withEndUserToken(CharSequence endUserToken)

      Returns a copy of this EndUserSecurityContext with a new end user token.

      Parameters:
      endUserToken - An end user token. Not null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new end user token.
    • endUserName

      public Optional<String> endUserName()

      Returns an Optional containing an end user name, Optional.empty() if the end user is identified by a security token instead of a name.

      Returns:
      The name of an end user, Optional.empty() if the end user is identified by a security token instead of a name.
    • withEndUserName

      public EndUserSecurityContext withEndUserName(String endUserName)

      Returns a copy of this EndUserSecurityContext with a new end user name and automatically generated look-up key. The name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.

      Parameters:
      endUserName - The name of an end user. Not null.
      Returns:
      A new EndUserSecurityContext with a new end user name and automatically generated look-up key. Not null.
    • withEndUserName

      public EndUserSecurityContext withEndUserName(String endUserName, CharSequence lookUpKey)

      Returns a copy of this EndUserSecurityContext with a new end user name and look-up key. The name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.

      Parameters:
      endUserName - The name of an end user. Not null.
      lookUpKey - A look-up key for the end user context. Not null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new end user name and look-up key. Not null.
    • lookUpKey

      public Optional<char[]> lookUpKey()

      Returns an Optional containing the look-up key for this EndUserSecurityContext.

      It is possible that the value returned by this method does not match the look-up key which Oracle Database associates to this EndUserSecurityContext. This can occur when an EndUserSecurityContext identifies the end user with a token, rather than a name, and the Oracle JDBC Driver version is different from the Oracle Database version. For example, the value will not match if the Oracle Database version is 23.26.2.0.0 and the Oracle JDBC version is 23.26.3.0.0 or newer. When an end user is identified by a token, any value returned by this method should be used only for diagnostics and testing. Critical application logic should not assume the value will be identical to the look-up key that a past or future version of Oracle Database will associate to this EndUserSecurityContext.

      This method is primarily intended for deconstruction of an EndUserSecurityContext when the end user is identified by a name, rather than a token. If the end user is identified by a name, this method is guaranteed to return a non-empty Optional containing the same look-up key which the database associates to this EndUserSecurityContext.

      Returns:
      An Optional containing a look-up key, or Optional.empty(). The CharSequence of a non-empty Optional is not retained.
    • dataRoles

      public Set<String> dataRoles()

      Returns an unmodifiable set of application managed data roles that have been enabled for the end user.

      This method does not query the database to obtain all data roles that are enabled for the end user; It only returns the names of data roles provided to methods such as withDataRoles(Collection).

      Returns:
      A set of DATA ROLE names. Not null.
    • withDataRoles

      public EndUserSecurityContext withDataRoles(String... dataRoles)

      Returns a copy of this EndUserSecurityContext with a new set of application managed data roles. A data role name should be enclosed in double-quotes if the DATA ROLE was declared using a quoted identifier.

      Parameters:
      dataRoles - An array of data role names. Not null, and must not contain null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new set of data roles. Not null.
    • withDataRoles

      public EndUserSecurityContext withDataRoles(Collection<String> dataRoles)

      Returns a copy of this EndUserSecurityContext with a new set of application managed data roles. A data role name should be enclosed in double-quotes if the DATA ROLE was declared using a quoted identifier.

      Parameters:
      dataRoles - A Collection of data role names. Not null, and must not contain null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new set of data roles. Not null.
    • attributes

      public Map<String,OracleJsonObject> attributes()

      Returns an unmodifiable Map of END USER CONTEXT attributes, represented as unmodifiable instances of OracleJsonObject. The Map returned by this method maps the name of an END USER CONTEXT to an OracleJsonObject containing the values of its attributes.

      This method does not query the database to obtain all attributes for an end user. It only returns attributes configured by methods such as withAttributes(Map).

      Returns:
      A Map containing the values of END USER CONTEXT attributes. The Map is not null, does not contain a null key, and does not contain null values.
    • withAttributes

      public EndUserSecurityContext withAttributes(String contextName, OracleJsonObject attributes)

      Returns a copy of this EndUserSecurityContext with a new set of attributes mapped to a fully qualified END USER CONTEXT name. A fully qualified name includes both a schema name and the END USER CONTEXT name, as in "app_schema.user_context" rather than "user_context". An end user context name should be enclosed in double-quotes if the END USER CONTEXT was declared using a quoted identifier.

      Parameters:
      contextName - The fully qualified name of an END USER CONTEXT. Not null.
      attributes - The attributes of an END USER CONTEXT. Not null. Not retained.
      Returns:
      A new EndUserSecurityContext with a new set of attributes. Not null.
    • withAttributes

      public EndUserSecurityContext withAttributes(Map<String,OracleJsonObject> attributes)

      Returns a copy of this EndUserSecurityContext with a new set of attributes mapped to their fully qualified END USER CONTEXT names. A fully qualified name includes both a schema name and the END USER CONTEXT name, as in "app_schema.user_context" rather than "user_context". An end user context name should be enclosed in double-quotes if an END USER CONTEXT was declared using a quoted identifier.

      Parameters:
      attributes - A mapping of fully qualified END USER CONTEXT names to their attributes. The Map must not be null and must not contain null keys or values. The Map and its OracleJsonObject values are not retained.
      Returns:
      A new EndUserSecurityContext with a new set of attributes. Not null.
    • equals

      public boolean equals(Object object)
      Returns true if an object is an EndUserSecurityContext encapsulating values which are all equal to those of this EndUserSecurityContext.
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Returns a hash code computed from all values encapsulated by this EndUserSecurityContext.
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Returns a String containing non-security-sensitive values encapsulated by this EndUserSecurityContext; This method will NOT return the clear text form of security tokens, look-up keys, or any other information that grants access to a secured system.
      Overrides:
      toString in class Object
    • createWithToken

      public static EndUserSecurityContext createWithToken(CharSequence databaseAccessToken, CharSequence endUserToken)

      Creates a security context in which an application with a database access token executes database operations for an end user identified by a security token.

      Parameters:
      databaseAccessToken - A security token that authorizes an application to access the database. Not null. Not retained.
      endUserToken - A security token that identifies an end user. Not null. Not retained.
      Returns:
      An EndUserSecurityContext. Not null.
    • createWithName

      public static EndUserSecurityContext createWithName(CharSequence databaseAccessToken, String endUserName)

      Creates a security context in which an application with a database access token executes database operations for an end user identified by a name. The look-up key of the EndUserSecurityContext is automatically generated by this class. The end user name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.

      Parameters:
      databaseAccessToken - A security token that authorizes an application to access the database. Not null. Not retained.
      endUserName - The name of an end user. Not null. Not retained.
      Returns:
      An EndUserSecurityContext. Not null.
    • createWithName

      public static EndUserSecurityContext createWithName(CharSequence databaseAccessToken, String endUserName, CharSequence lookUpKey)

      Creates a security context in which an application with a database access token executes database operations for an end user identified by a name. The look-up key of the EndUserSecurityContext is provided to this method, rather than automatically generated. The end user name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.

      Parameters:
      databaseAccessToken - A security token that authorizes an application to access the database. Not null. Not retained.
      endUserName - The name of an end user. Not null.
      lookUpKey - The look-up key of the EndUserSecurityContext. Not null. Not retained.
      Returns:
      An EndUserSecurityContext. Not null.