Package oracle.jdbc

Class EndUserSecurityContext


  • public final class EndUserSecurityContext
    extends java.lang.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., Deep Sec).

    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 attributes to all database operations executed with an EndUserSecurityContext having the same look-up key.
    • For an EndUserSecurityContext that identifies an end user with a security token, its look-up key is automatically generated by the database.
      • The generated key is derived from the database access token, end user token, and application managed data roles.
      • The look-up key cannot be accessed using the lookUpKey() method because the value is generated by the database, not the application.
    • For an EndUserSecurityContext that identifies an end user with a name, its look-up key may be automatically generated by an instance of this class or provided by an application.
      • An automatically generated look-up key is derived from the database access token, end user name, and application managed data roles.
      • The look-up key can be accessed using the lookUpKey() method because this value is generated by the application, not by the database.
    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 Detail

      • databaseAccessTokenExpiration

        public java.time.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​(java.lang.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 java.time.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 java.util.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​(java.lang.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 java.util.Optional<java.lang.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​(java.lang.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​(java.lang.String endUserName,
                                                      java.lang.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 java.util.Optional<char[]> lookUpKey()

        Returns an Optional containing the look-up key for this EndUserSecurityContext if the end user is identified by a name, or Optional.empty() if the end user is identified by a token instead of a name.

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

        public java.util.Set<java.lang.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​(java.lang.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​(java.util.Collection<java.lang.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 java.util.Map<java.lang.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​(java.lang.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​(java.util.Map<java.lang.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​(java.lang.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 java.lang.Object
      • hashCode

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

        public java.lang.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 java.lang.Object
      • createWithToken

        public static EndUserSecurityContext createWithToken​(java.lang.CharSequence databaseAccessToken,
                                                             java.lang.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. The look-up key of the EndUserSecurityContext is automatically generated by the database.

        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​(java.lang.CharSequence databaseAccessToken,
                                                            java.lang.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​(java.lang.CharSequence databaseAccessToken,
                                                            java.lang.String endUserName,
                                                            java.lang.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.