4 Developing Services Gatekeeper Services Using OAuth

This chapter explains the options you have for customizing the Open Authorization Protocol v2.0 (OAuth) functionality for use with Oracle Communications Services Gatekeeper.

Understanding How to Apply SAML Tokens

Network-originated request messages that apply a security assertion markup language (SAML) token must use the structure explained in this section.

Understanding Token Request Messages

Method Type: POST

URL: http://Gatekeeper_IPaddress:Gatekeeper_port/oauth2/saml

Request Parameters:

  • grant_type - Required. Must use this value: urn:ietf:params:oauth:grant-type:saml2-bearer

  • client_id - Optional. The client identifier.

  • scope - Optional. A value defined by the authorization server.

  • assertion - Required. The assertion being used as an authorization grant. The serialization must be encoded for transport within HTTP forms. Oracle recommends that you use base64url (defined in RFC 4648) to avoid unnecessarily long strings.

Example 4-1 shows an example SAML token request message.

Example 4-1 SAML Token Request Message Example

   POST /token HTTP/1.1
   Host: server.example.com
   Content-Type: application/x-www-form-urlencoded
 
   client_id=s6BhdRkqt3&
   grant_type=urn%3Aoasis%3Anames%sAtc%3ASAML%3A2.0%3Aassertion&
   assertion=PHNhbWxwOl...[omitted for brevity]...ZT4

Example 4-2 shows a sample SAML assertion string before encoding.

Example 4-2 SAML Token Assertion String Example

<Assertion IssueInstant="2010-10-01T20:07:34.619Z"
ID="ef1xsbZxPV2oqjd7HTLRLIBlBb7"
Version="2.0"
xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>https://saml-idp.example.com</Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  [...omitted for brevity...]
  </ds:Signature>
  <Subject>
    <NameID
     Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
     brian@example.com
    </NameID>
    <SubjectConfirmation
        Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
     <SubjectConfirmationData
        NotOnOrAfter="2010-10-01T20:12:34.619Z"
        Recipient="https://authz.example.net/token.oauth2"/>
     </SubjectConfirmation>
  </Subject>
  <Conditions>
    <AudienceRestriction>
     <Audience>https://saml-sp.example.net</Audience>
    </AudienceRestriction>
  </Conditions>
  <AuthnStatement AuthnInstant="2010-10-01T20:07:34.371Z">
     <AuthnContext>
       <AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:X509
     </AuthnContextClassRef>
  </AuthnContext>
 </AuthnStatement>
</Assertion>

Understanding Token Response Messages

Method type: HTTP/1.1 200 OK

Response Parameters:

  • access_token - Required. The access token issued by the authorization server.

  • token_type - Required. Normally bearer or mac. But in the SAML flow, the token type could be extended to be bearer or saml-bearer.

  • expires_in - Required. The amount of time, in seconds, that the access token is valid.

  • scope - Required if different from the request message scope. Otherwise optional.

Example 4-3 shows an example SAML token response message.

Example 4-3 SAML Token Response Message

  HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache
 
     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"bearer",
       "expires_in":3600
     }

Understanding SAML Assertion Validation Messages

Table 4-1 lists the SAML assertion validation check point processing rules that your applications send.

Table 4-1 SAML Assertion Validation Check Point Processing Rules

Checkpoint Processing Rule

Issuer

The assertion validation <Issuer> element must contain a unique identifier for the entity that issued the assertion. Services Gatekeeper confirms that the value for this checkpoint is included in the trusted issuers list.

Audience

The assertion must contain a <Conditions> element which includes an <AudienceRestriction> element, which in turn, contains an <Audience> element with a URI reference. The token endpoint URL of the authorization server may be used as an acceptable value for an <Audience> element. The authorization server must verify that it is an intended audience for the assertion.

Subject

The assertion must contain a <Subject> element. The <Subject> element may identify the resource owner for whom the access token is being requested.

NotOnORAfter

The assertion must have an expiration time that limits the time window during which it can be used. The time limit can be expressed either as the NotOnOrAfter attribute of the <Conditions> element or as the NotOnOrAfter attribute of a

suitable <SubjectConfirmationData> element.

Method

The <Subject> element must contain at least one <SubjectConfirmation> element that allows the authorization server to confirm it as a bearer assertion. The <SubjectConfirmation> element must have a Method attribute with a value of urn:oasis:names:tc:SAML:2.0:cm:bearer.

Signature

The assertion must be digitally signed by the issuer and the authorization server MUST verify the signature. Services Gatekeeper validates the signature and confirm that the trusted certificate is identical to the attached certificate in the assertion.


Example 4-4 shows an example SAML assertion validation message.

Example 4-4 Example SAML Assertion Validation Message

<Assertion IssueInstant="2010-10-01T20:07:34.619Z"
ID="ef1xsbZxPV2oqjd7HTLRLIBlBb7"
Version="2.0"
xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>https://saml-idp.example.com</Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  [...omitted for brevity...]
  </ds:Signature>
  <Subject>
    <NameID
     Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
     brian@example.com
    </NameID>
    <SubjectConfirmation
        Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
     <SubjectConfirmationData
        NotOnOrAfter="2010-10-01T20:12:34.619Z"
        Recipient="https://authz.example.net/token.oauth2"/>
     </SubjectConfirmation>
  </Subject>
  <Conditions>
    <AudienceRestriction>
     <Audience>https://saml-sp.example.net</Audience>
    </AudienceRestriction>
  </Conditions>
  <AuthnStatement AuthnInstant="2010-10-01T20:07:34.371Z">
     <AuthnContext>
       <AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:X509
     </AuthnContextClassRef>
  </AuthnContext>
 </AuthnStatement>
</Assertion>

Understanding OAuth Customization

This section contains information on customizing the Service Gatekeeper OAuth functionality. It starts by explaining the customization options:

This chapter also explains how to develop applications using OAuth:

Implementing a Third-Party Authentication Service

You can delegate authentication to a third-party authentication service provider instead of with the default Subscriber Management Service. The authentication provider is responsible for the resource owner identity validation and handling the grant collection flow.

A delegated authentication service used with Services Gatekeeper is responsible for:

  1. Hosting the Authentication Endpoint.

  2. Presenting the expanded scope and authenticating a resource owner.

  3. Redirecting the resource owner to the grant endpoint hosted by Services Gatekeeper upon successful authentication of the resource owner.

See "Understanding the OAuth Endpoints" for more information.

Authentication Process Flow

This section describes the flow of requests between Services Gatekeeper and a delegated authentication service. Sample responses to the requests along with a description of the flow are provided.

  1. An application sends a standard OAuth Authorization request to Services Gatekeeper in a format that looks like this:

    GET /oauth2/authorize?client_id=client123&redirect_
    uri=https://www.google.com/asdf&response_
    type=code&scope=POST-/payment/acr:Authorization/transactions/amount&state=123
    HTTP/1.1
    

    After receiving the OAuth2 authorization request, Services Gatekeeper add more detailed information to it using the client_id and scope request parameters. This additional information is appended to the location header in the 302 redirect response directed to the configured authentication endpoint.

    The location header contains these elements:

    • The delegating authentication endpoint

    • The original OAuth Authentication request parameters

    • The Grant endpoint

    • Detailed information for the client_id and scope parameters.

    This is an example 302 response:

    HTTP/1.1 302 Moved Temporarily
    Location: https://authentication_url?client_
    id=client123&redirect_uri=https://www.google.com/asdf&response_
    type=code&scope=POST-/payment/acr:Authorization/transactions/
    amount&state=123&grant_url=grant&client_info=%7B%22clientId%22%3A%22client123%22%2C%22clientName
    %22%3A%22client123%22%2C%22clientDescription
    %22%3A%22client123+desc%22%7D&scopes_info=%5B%7B
    %22scopeId%22%3A%22POST-%2Fpayment
    %2Facr%3AAuthorization%2Ftransactions
    %2Famount%22%2C%22scopeDescription
    %22%3A%22Charge+or+refund%22%2C%22parameters
    %22%3A%5B%7B%22code%22%3A%22billable+item+id%22%7D%5D%7D%5D
    

    In addition to the original OAuth authorization request parameters, the detailed format specifications of all additional parameters are defined in Table 4-2:

    Table 4-2 OAuth Authorization Request Parameters

    Parameter Description

    grant_url

    The URL can be submitted later according resource owner's approval. See "Understanding the OAuth Endpoints" for more information.

    client_info

    Client information will be constructed into a JSON Object as shown below. Encoding complies with the following specification:

    http://www.w3.org/Addressing/URL/url-spec.html

    {
     "clientId":"client123",
     "clientName":"Oracle",
     "clientDescription":"Oracle Description"
    }
    

    scopes_info

    scope information will be constructed into a JSON Object as shown below. Encoding complies with the following specification:

    http://www.w3.org/Addressing/URL/url-spec.html

    [
      {
        "scopeId":"POST- payment acr:Authorization transactions amount",
        "scopeDescription":"Charge+or+refund",
        "parameters":[{"code":"billable+item+id"}]
     }
    ]
    

  2. The resource owner's browser continues to access the authentication endpoint identified in the Location header.

    The authentication endpoint should accept the redirected request, authenticate the resource owner for proper credentials and render an interactive graphical interface for authorization. The resource owner can use the information in the interface to understand the scope and client information of the authorization request and determine if the request should be authorized.

    After the resource owner authorizes the scope, the authentication endpoint redirects the resource owner to the Grant endpoint with the parameters listed in Table 4-3 through an HTTP POST operation. The OAuth flow continues normally after redirecting the resource owner toward the Grant endpoint. The client then receives the authorization code at the Redirect endpoint.

    Table 4-3 lists the OAuth grant endpoint POST parameters.

    Table 4-3 OAuth Grant Endpoint POST Parameters

    Parameter Description

    user_address

    Address of resource owner

    grant_scopes

    The scope that the resource owner grants to the application. The value of the scope parameter is expressed as a list of space-delimited strings. Each string adds an additional access range to the selected scope parameter.

    According to the resource owner decisions at the Authentication endpoint, the granted scope can be narrower than the originally requested scope. Services Gatekeeper rejects a granted scope that is wider than originally requested scope.

    Based on the implementation of the Authentication endpoint and the resource owner interaction, additional parameter may be appended to each scope id. These scope parameters will be available to an interceptor so that stricter enforcement can be applied according to different parameters.

    The scope format is:

    scopeId?[<param>=<value>[&<param>=<value>]*].

    For example:

    grant_scopes=chargeAmount?maxAmount=100&minAmount=100 getLocation?requestedAccuracy=100 sendSMS 
    

    response_type

    As in the first authorization request

    client_id

    As in the first authorization request

    redirect_uri

    As in the first authorization request

    state

    As in the first authorization request

    scope

    As in the first authorization request


Creating an OAuth Interceptor

This section describes the basic principles for creating a custom OAuth interceptor.

As described in "Implementing a Third-Party Authentication Service", it is possible to add additional parameters to the scope-token so that custom interceptors can be created for fine-grained resource access and traffic control.

Table 4-4 lists the OAuth parameters available in the RequestContext object for an OAuth enabled communication service. Customized interceptors can make use of these parameters to further fine tune authorized access to protected resources.

For information on creating custom interceptors, see ”Creating and Using Custom Interceptors” inServices Gatekeeper Extension Developer's Guide.

Table 4-4 OAuth RequestContext Parameters

Attribute Name Access Type Description

OAUTH2_SCOPE_PARAMETER

read only

java.util.Map

Contains all parameters of the current request scope.

CONTEXT_OAUTH2_RESOURCE_OWNER

read only

java.lang.String

The resource owner of the token, which is usually the same as the address in the request. When the resource owner is a group URI or the scheme of address in request is ACR, they may be different.

CONTEXT_OAUTH2_PARAMETER

read only

java.util.Map

Contains all endpoint parameters of this request. This parameter must start with oracle_ or ocsg_.

CONTEXT_OAUTH2_STATE

read/write

java.util.Map

Values of this attribute will be available during the lifecycle of one OAuth access token.


Examples: Using a Custom OAuth Interceptor to Retrieve OAuth Information

This example shows how to retrieve and use OAuth associated information from the requestContext within a customized interceptor.

To retrieve the MSIDN of an OAuth resource owner:

/**
* The following example shows a way to retrieve Oauth2 resource owner MSIDN
*/@Override
public Object invoke(final Context context) throws Exception {
  String currentResourceOwner = (String) context.getRequestContext()
.get(”CONTEXT_OAUTH2_RESOURCE_OWNER”);
  If (currentResourceOwner == null)
throw new DenyPluginException(”Not a OAuth based request!”);
  else
System.out.println(”Current Oauth2 resource owner is:” + currentResourceOwner);
  context.invokeNext(this);
}

To control the maximum charged value, use an additional scope parameter called maxAmount:

/**
* The following example shows a way to control the maximum charged value using
additional scope parameter* "maxAmount". 
*/
@Override
public Object invoke(final Context context) throws Exception {
if (context.getType().equals(AmountCharingPlugin.class)) {Map<String, String> scopeParameters = (Map<String,
String>)context.getRequestContext().get("OAUTH2_SCOPE_PARAMETER");
int maxAmount = Integer.parseInt(scopeParameters.get("maxAmount").toString());

if (((ChargeAmount)context.getArguments()[0]).getAmount() > maxAmount)
throw new DenyPluginException("Specified chargeAmount request exceed
limitation.");
 }
  context.invokeNext(this);}

Integrating a Third-Party Subscriber Repository

Service Gatekeeper offers the flexibility to integrate with custom subscriber repositories for user authentication. You can develop a customized Subscriber Manager to authenticate users against external subscriber repositories such as LDAP.

Developing a custom Subscriber Manager involves the following steps:

  1. Implementing oracle.Services Gatekeeper.subscriber.SubscriberManager, and customizing the implementation of this interface

  2. Registering the custom implementation with the OAuth security framework with this method.

    void oracle.Services
    Gatekeeper.subscriber.SubscriberManager.registerInstance("default",
    SubscriberManager instance);
    

    For additional information on customizing the default SubscriberManager, including method details, see SubscriberManager in the ”All Classes” section of the Services Gatekeeper Java API Reference.

Creating an OAuth Extension Handler for New Credentials

Services Gatekeeper supports a rich set of credential types that OAuth uses for security. However, if your implementation uses new or evolving credential standards that Services Gatekeeper does not support, you have the option to create a customized extension handler to use them. The Platform Development Studio enables you to customize endpoint parameters, grant types, response types, or errors using the OAuth2 Extension Handlers wizard.

For details see ”Generating an OAuth 2.0 Extension Handler” in Services Gatekeeper Extension Developer's Guide.

Customizing OAuth Resource Grant Tests

Services Gatekeeper enables you to add your own customized tests to OAuth resource requests. For example, you may want to restrict access to a resource to just subscribers from a specific block of email addresses. There are two ways to add the customized tests:

  • Using an auth.jsp file which you then reference using a REGEX_MATCH parameter in the resource XML file. Services Gatekeeper then perform any tests (regular expressions) that you have added to the jsp file. Each subscriber must pass the tests in this file to get access to the resource. For details, see Oracle Fusion Middleware Developing Web Applications, Servlets, and JSPs for Oracle WebLogic Server at:

    https://docs.oracle.com/cd/E24329_01/web.1211/e21049/toc.htm

  • Creating a custom services interceptor. For details see ”Using Service Interceptors to Manipulate Requests” in Services Gatekeeper Extension Developer's Guide

OAuth Application Developer Guide

This section contains information useful for application developers using OAuth with Services Gatekeeper.

Interacting with the Services Gatekeeper OAuth Service

This section describes the available OAuth endpoints, the steps involved in obtaining an access token, and how applications use access tokens to access a REST resource in Services Gatekeeper.

The following endpoints are available in the OAuth Service:

  • Authorization endpoint

  • Token endpoint

  • Authentication endpoint

  • Grant endpoint

Figure 4-1 demonstrates the end to end flow to obtain an access token and use the access token to access the resource.

Figure 4-1 OAuth Endpoints and Functional Responsibility

Description of Figure 4-1 follows
Description of ''Figure 4-1 OAuth Endpoints and Functional Responsibility''

OAuth Access Flow In Services Gatekeeper

This procedure describes the OAuth access flow:

  1. A resource owner visits an application website and initiates a request that requires granting access to protected resources, to an application.

  2. The application redirects the resource owner to the Authorization endpoint with the application information including the client id and scope id.

    For example, the application can provide a link to trigger a HTTP GET request where the following information is included in the HTTP query string:

    • HTTP Request: GET

    • URI: https://host:port/oauth2/authorize

    • Parameters:

      • response_type -- Supported values are code or token

      • client_id. -- The client identifier

      • redirect_uri -- Required

      • scope -- The scope of the access request expressed as a list of space-delimited, case sensitive strings. The Services Gatekeeper Authorization Server accepts zero to multiple scope-tokens in the following format for scope-token:

        <scopeId>[?<param>=<value>[&<param>=<value>]*]+

        Where scopeId is the resource identifier and param is the name of one of the allowed parameters defined as part of resource.

        For example:

        chargeAmount?code=1976

        An example scope would look like:

        GET /oauth2/authorize?response_type=code&client_id=app123&state=xyz 
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
        Host: server.Services Gatekeeper.com
        
  3. Services Gatekeeper validates the resource owner identity and obtains the resource owner's consent on the requested scope.

  4. The application exchanges the authorization code for an authorization token using the Token Endpoint. Services Gatekeeper server returns the token directly.

    The request can be described as follows:

    • HTTP Request: POST

    • URI: https://<AT_HOST>:<AT_PORT>/oauth2/token

    • Parameters:

      • grant_type: Value can be set to authorization_code, if the request is not a SAML assertion.

      • code: The authorization code received from the Authorization Server.

      • redirect_uri: The redirection URI used by the Authorization Server to return the authorization response in the previous step.

      • client_id: The client identifier.

      • client_secret: The client password.

    • Authorization Header:

      The client application may use the HTTP basic authentication scheme as defined in RFC2617 to authenticate with the Services Gatekeeper server. The client_id is used as the username and the client_secret is used as the password.

      For example:

      Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

      Alternatively, the Authorization Server may support including the client credentials in the request body using these parameters:

      • client_id: The client identifier.

      • client_secret: The application password.

    • HTTP Response:

      • access_token: The authorization code generated by Services Gatekeeper.

      • token_type: The Bearer or MAC authorization code received from Services Gatekeeper.

      • expires_in: The duration in seconds of the access token lifetime.

      • refresh_token: The refresh token which you use to obtain new access tokens using the same authorization grant.

      • scope: The scope of the access request expressed as a list of space-delimited, case sensitive strings.

      • anonymous_id: Uniquely identifies the resource owner.

      • mac_key: The MAC key verifies the later request of access protect resource. (MAC-Type access token).

      • mac_algorithm: The MAC algorithm used to calculate the request MAC. The value must be either hmac-sha-1 or hmac-sha-256.

      The response is different depending on the token type submitted in the request.

      This is an example for a Bearer-Type Access Token with HTTP Basic Authentication:

      Request:

      POST /oauth2/token HTTP/1.1
      Host: localhost:7999
      Content-length: 128
      Authorization: Basic YXBwMTIzOmFwcDEyMw==
      Content-Type: application/x-www-form-urlencoded
      Connection: Close
      
      grant_type=authorization_
      code&code=75dfe1c9-9784-4545-846f-e1493f087017&redirect_
      uri=http%3A%2F%2Flocalhost%2Fapp%2Fredirect.php
      

      Response:

      HTTP/1.1 200 OKCache-Control: no-storeConnection: closeContent-Length: 327Content-Type: application/json{"access_token":"44fb85f8-e400-41b3-9bd4-68617d131039","token_
      
      type":"MAC","expires_
      in":3600,"scope":"POST-/payment/acr:Authorization/transactions/amount",
      "mac_algorithm":"hmac-sha-1","mac_
      key":"-3677656698299327487","secret":"-3677656698299327487",
      "anonymousid":"1debde44-f9d4-41d6-88fe-bbb77fea37c8",
      "algorithm":"hmac-sha-1"}
      

      The following example is for a MAC-Type Access Token with included client credentials in the request body.

      Request:

      POST /oauth2/token HTTP/1.1
      Host: server.Services Gatekeeper.com
      Content-Type: application/x-www-form-urlencoded
         grant_type=authorization_code&client_id=app123&client_secret=
      app123&code=i1WsRn1uB1&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
      

      Response:

      HTTP/1.1 200 OK
      Content-Type: application/json
      Cache-Control: no-store
      
      {  "access_token":"SlAV32hkKG","token_type":"mac",    "expires_in":3600,
      "refresh_token":"8xLOxBtZp8","secret":"23sasd#adf@#" 
      "algorithm":"hmac-sha-1"}
      
  5. The application can now access the protected resource.

    The application needs to add an HTTP Authorization Header when accessing the granted resource. The value of authorization head depends on the access token type.

    For a Bearer token, the application can directly transmit the access token using an HTTP authorization header in the request.

    For a MAC token, the application constructs the HTTP authorization header using a MAC key with the access token. For more information, see:

    http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-00.

    Below is a sample PHP code snippet illustrating the insertion of the token in the HTTP Authorization Header:

    $body_hash=base64_encode(hash('sha1',$http_body,true));
    $payload=$nonce."\n".$http_method."\n".$request_path."\n".$host_name."\n".$host_port."\n".$body_hash."\n".$ext."\n";
    $mac = base64_encode(hash_hmac('sha1', $payload, $mac_key, true));
    $oauth2_header='MAC id="'.$mac_key_id."\",nonce=\"".$nonce."\",bodyhash=\"".$body_hash."\",mac=\"".$mac.'"';
    

    This example request contains a Bearer authorization token:

    POST /oneapi/1/payment/acr%3AAuthorization/transactions/amount HTTP/1.1
            Host: localhost:7999
            Content-Type: application/x-www-form-urlencoded
            Authorization: Bearer vF9dft4qmT
            
            {"amountTransaction":{"endUserId":"acr:Authorization",
    "paymentAmount":{"chargingInformation":{"description":"chargeAmount",
    "currency":"USD",
    "amount":"2","code":""},
    "chargingMetaData":{"onBehalfOf":"Example Games Inc",
    "purchaseCategoryCode":"Game",
    "channel":"",
    "taxAmount":"0",
    "mandateId":"",
    "serviceId":"",
    "productId":""}},
    "transactionOperationStatus":"Charged",
    "referenceCode":"REF-12345",
    "clientCorrelator":""}
    }
    

    The following example request contains a MAC authorization token:

    POST /oneapi/1/payment/acr%3AAuthorization/transactions/amount HTTP/1.1
    Host: localhost:7999
    Content-length: 415
    Authorization: MAC id="176c04f0-d4d4-4385-b2d6-b19649f21b78",
    nonce="273156:di3hvdf8",
    bodyhash="junEVZu4M9q1qVaxAByY7lYQun8=",
    mac="TudmT3bM5UgqvkL8nq1EuhcZ6O8="
    Content-Type: application/json
    X-Session-ID: app:-7562122823730178188
    Connection: Close
     
    {"amountTransaction":{"endUserId":"acr:Authorization",
    "paymentAmount":{"chargingInformation":{"description":"chargeAmount",
    "currency":"USD",
    "amount":"2",
    "code":""},
    "chargingMetaData":{"onBehalfOf":"Example Games Inc",
    "purchaseCategoryCode":"Game",
    "channel":"",
    "taxAmount":"0",
    "mandateId":"",
    "serviceId":"",
    "productId":""}},
    "transactionOperationStatus":"Charged",
    "referenceCode":"REF-"
    }