diff --git a/samples/oci-apigw-idcs-auth-basic/README.md b/samples/oci-apigw-idcs-auth-basic/README.md index 5d19563..f750a31 100644 --- a/samples/oci-apigw-idcs-auth-basic/README.md +++ b/samples/oci-apigw-idcs-auth-basic/README.md @@ -1,62 +1,70 @@ -# API Gateway Basicauth function using IDCS -This function provides verification of username and password against IDCS at runtime and allows only authorized users to access API gateway deployment. +# API Gateway Basicauth function using Identity Cloud Service (IDCS) -The implementation conforms to the guidelines in the OCI Documentation at https://docs.cloud.oracle.com/en-us/iaas/Content/APIGateway/Tasks/apigatewayusingauthorizerfunction.htm. +This function provides verification of username and password against IDCS at runtime and allows only authorized users to access API gateway deployment. + +The implementation conforms to the [documented guidlines for using authorizer functions to add Authentication and Authorization to API deployments](https://docs.cloud.oracle.com/en-us/iaas/Content/APIGateway/Tasks/apigatewayusingauthorizerfunction.htm). As you make your way through this tutorial, look out for this icon ![user input icon](./images/userinput.png). Whenever you see it, it's time for you to perform an action. - ## Prerequisites [Create users in IDCS](https://docs.oracle.com/en/cloud/paas/identity-cloud/uaids/create-user-accounts.html) -Before you deploy this sample function, make sure you have run step A, B and C of the [Oracle Functions Quick Start Guide for Cloud Shell](https://www.oracle.com/webfolder/technetwork/tutorials/infographics/oci_functions_cloudshell_quickview/functions_quickview_top/functions_quickview/index.html) -* A - Set up your tenancy -* B - Create application -* C - Set up your Cloud Shell dev environment +Before you deploy this sample function, you need to complete steps A, B and C of the [Oracle Functions Quick Start Guide for Cloud Shell](https://www.oracle.com/webfolder/technetwork/tutorials/infographics/oci_functions_cloudshell_quickview/functions_quickview_top/functions_quickview/index.html) + +- A - Set up your tenancy +- B - Create application +- C - Set up your Cloud Shell dev environment ## List Applications + Assuming your have successfully completed the prerequisites, you should see your application in the list of applications. + ``` fn ls apps ``` ## Deploy a function that implements an API + We need another function that will be a target for API Gateway. We suggest [oci-display-httprequest-info-python](../oci-display-httprequest-info-python). -In Cloud Shell, run the *fn deploy* command to build the function and its dependencies as a Docker image, -push the image to OCIR, and deploy the function to Oracle Functions in your application. +In Cloud Shell, run `fn deploy` to build the function and its dependencies as a container, +push the image to Oracle Cloud Infrastructure Registry (OCIR), and deploy the function to Oracle Functions in your application. ![user input icon](./images/userinput.png) + ``` cd ../oci-display-httprequest-info-python fn -v deploy --app ``` ## Create or Update your Dynamic Group for API Gateway + In order to invoke functions, your API Gateway must be part of a dynamic group. -When specifying the *Matching Rules*, we suggest matching all functions in a compartment with: +When specifying the _Matching Rules_, we suggest matching all functions in a compartment with: + ``` ALL {resource.type = 'ApiGateway', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaxxxxx'} ``` - ## Create or Update IAM Policies for API Gateway + Create a new policy that allows the API Gateway dynamic group to invoke functions. We will grant `use` access to `functions-family` in the compartment. ![user input icon](./images/userinput.png) Your policy should look something like this: + ``` Allow dynamic-group to use functions-family in compartment ``` For more information on how to create policies, check the [documentation](https://docs.cloud.oracle.com/iaas/Content/Identity/Concepts/policysyntax.htm). - ## Configure Identity Cloud Service (IDCS) + Login to IDCS admin console and create, add an Application and select "Confidential Application". ![IDCS-appcreate0](./images/IDCS-appcreate0.png) @@ -64,118 +72,133 @@ Enter a name for your IDCS Application, for example "myAPI". ![IDCS-appcreate1](./images/IDCS-appcreate1.png) -For "Allowed Grant Types", select "Resource Owner". Click *Next*. +For "Allowed Grant Types", select "Resource Owner". Click _Next_. ![IDCS-appcreate2](./images/IDCS-appcreate2.png) For Primary Audience, enter anything "display-httprequest-info" for example. -For Scopes, click *Add*. In the dialog box, for field "Scope", enter anything "display-httprequest-info" for example, click *Add*. +For Scopes, click _Add_. In the dialog box, for field "Scope", enter anything "display-httprequest-info" for example, click _Add_. ![IDCS-appcreate3](./images/IDCS-appcreate3.png) -Click *Next*. +Click _Next_. ![IDCS-appcreate4](./images/IDCS-appcreate4.png) -Click *Finish*. +Click _Finish_. ![IDCS-appcreate5](./images/IDCS-appcreate5.png) -Now that the application is added, note the *Client ID* and *Client Secret*. +Now that the application is added, note the _Client ID_ and _Client Secret_. ![IDCS-appcreate6](./images/IDCS-appcreate6.png) -Click *Close*. +Click _Close_. -Click on Configurations tab under Client Information section click on add scope and select the *application name* from the dropdown. Note the scope value. +Click on Configurations tab under Client Information section click on add scope and select the _application name_ from the dropdown. Note the scope value. ![IDCS-appcreate7](./images/IDCS-appcreate7.png) ![IDCS-appcreate8](./images/IDCS-appcreate8.png) -Click *Activate* and click *Ok* in the dialog. - -Note the *IDCS URL*, this is the URL you see in your browser URL bar, copy the IDCS url ( For example: https://idcs-xxxxxxxxxxx.identity.oraclecloud.com/ ), client-id, client-secret and scope these values are provided to the Basicauth function. - +Click _Activate_ and click _Ok_ in the dialog. +Note the _IDCS URL_, this is the URL you see in your browser URL bar, copy the IDCS url ( For example: https://idcs-xxxxxxxxxxx.identity.oraclecloud.com/ ), client-id, client-secret and scope these values are provided to the Basicauth function. ## Review and customize the function + Review the following files in the current folder: -- [pom.xml](./pom.xml) specifies all the dependencies for your function -- [func.yaml](./func.yaml) that contains metadata about your function and declares properties -- [src/main/java/com/example/fn/BasicAuth.java](./src/main/java/com/example/fn/BasicAuth.java) which contains the Java code -The name of your function *basicauth* is specified in [func.yaml](./func.yaml). +- [`pom.xml`](./pom.xml) specifies all the dependencies for your function +- [`func.yaml`](./func.yaml) that contains metadata about your function and declares properties +- [`src/main/java/com/example/fn/BasicAuth.java`](./src/main/java/com/example/fn/BasicAuth.java) which contains the Java code -set the following variable in "src/main/java/com/example/utils/ResourceServerConfig.java" to the values noted while configuring IDCS. -``` -public static final String CLIENT_ID = "xxxxxxxxxxx"; -public static final String CLIENT_SECRET = "xxxxxxxxx"; -public static final String IDCS_URL = "https://idcs-xxxxxxxx.identity.oraclecloud.com"; +The name of your function `basicauth` is specified in [`func.yaml`](./func.yaml). + +set the following config variables to the values noted while configuring IDCS. The IDCS URL is the token endpoint that returns the access token after validating credentials + +```java +CLIENT_ID = "xxxxxxxxxxx"; +CLIENT_SECRET = "xxxxxxxxx"; +IDCS_URL = "https://idcs-xxxxxxxx.identity.oraclecloud.com/oauth2/v1/token"; //INFORMATION ABOUT THE TARGET APPLICATION -public static final String SCOPE_AUD = "display-httprequest-infodisplay-httprequest-info"; +SCOPE_AUD = "display-httprequest-infodisplay-httprequest-info"; ``` +For the unit test to run, set the following variables in src/test/java/com/example/fn/BasicAuthTest.java + +```java + private static final String TEST_IDCS_URL = "https://idcs-xxxxxxxx.identity.oraclecloud.com/oauth2/v1/token"; + private static final String TEST_CLIENT_ID = "xxxxxxxxxxx"; + private static final String TEST_CLIENT_SECRET = "xxxxxxxxxxx"; + private static final String TEST_SCOPE_AUD = "display-httprequest-infodisplay-httprequest-info"; + private static final String TEST_TOKEN = "xxxxxxxxxxx"; +``` ## Deploy the basicauth function -In Cloud Shell, run the *fn deploy* command to build the function and its dependencies as a Docker image, + +In Cloud Shell, run `fn deploy` to build the function and its dependencies as a container, push the image to OCIR, and deploy the function to Oracle Functions in your application. ![user input icon](./images/userinput.png) -``` + +```shell fn -v deploy --app ``` + ## Invoke the basicauth function in cloud shell -In Cloud Shell, run *fn invoke* command to invoke the deployed function, returns active status as true if the token is valid or else returns false. + +In Cloud Shell, run `fn invoke` to invoke the deployed function. It should return an active status of true if the token is valid or otherwise returns false. ![user input icon](./images/userinput.png) -``` + +```shell echo -n '{"type":"TOKEN", "token":"Basic aW5jaGFyYS5zaGFtYW5uYUBvcmFj....."}' | fn invoke ``` ## Create the API Gateway + The functions is meant to be invoked through API Gateway. ![user input icon](./images/userinput.png) -On the OCI console, navigate to *Developer Services* > *API Gateway*. Click on *Create Gateway*. Provide a name, set the type to "Public", select a compartment, a VCN, a public subnet, and click *Create*. +On the OCI console, navigate to _Developer Services_ > _API Gateway_. Click on _Create Gateway_. Provide a name, set the type to "Public", select a compartment, a VCN, a public subnet, and click _Create_. ![APIGW create](./images/apigw-create.png) -Once created, click on your gateway. Under *Resources*, select *Deployments* and click *Create Deployment*. +Once created, click on your gateway. Under _Resources_, select _Deployments_ and click _Create Deployment_. -* Provide a name, a path prefix ("/basicauth" for example). -* Under *API Request Policies* Add Authentication - * Authentication Type: *Custom* - * Choose the application and the basicauth function - * For "Authentication token", select *Header* - * For the "Header Name", enter "Autorization" +- Provide a name, a path prefix ("/basicauth" for example). +- Under _API Request Policies_ Add Authentication + - Authentication Type: _Custom_ + - Choose the application and the basicauth function + - For "Authentication token", select _Header_ + - For the "Header Name", enter "Autorization" -Click *Save Changes* when you are finished +Click _Save Changes_ when you are finished ![APIGW deployment create](./images/apigw-deployment-create.png) -Click *Next*. Provide a name to the route ("/hello" for example), select methods eg: "GET", select *HTTP-URL* for your back-end. +Click _Next_. Provide a name to the route ("/hello" for example), select methods eg: "GET", select _HTTP-URL_ for your back-end. ![APIGW deployment create](./images/apigw-deployment-create-route.png) -Click *Next* and finally, click *Save Changes*. +Click _Next_ and finally, click _Save Changes_. Note the endpoint of your API Gateway deployment. ![APIGW deployment endpoint](./images/apigw-deployment-endpoint.png) - ## Invoke the Deployment endpoint + The function validates if the user information is valid. ![user input icon](./images/userinput.png) -Use the curl command to make the HTTP request -``` +Use `curl` to make the HTTP request + +```shell curl -i -u ":" https://d6xxxxxxxxk64.apigateway.us-ashburn-1.oci.customer-oci.com/basicauth/hello ``` + If the user is valid gateway will make a call to backend with HTTP200 else The gateway will reject the request with an HTTP401. - - - diff --git a/samples/oci-apigw-idcs-auth-basic/func.yaml b/samples/oci-apigw-idcs-auth-basic/func.yaml index 81bdb74..6f30b1b 100644 --- a/samples/oci-apigw-idcs-auth-basic/func.yaml +++ b/samples/oci-apigw-idcs-auth-basic/func.yaml @@ -1,7 +1,9 @@ schema_version: 20180708 name: basicauth -version: 0.0.6 +version: 0.0.21 runtime: java build_image: fnproject/fn-java-fdk-build:jdk11-1.0.146 run_image: fnproject/fn-java-fdk:jre11-1.0.146 cmd: com.example.fn.BasicAuth::handleRequest +memory: 512 +timeout: 120 diff --git a/samples/oci-apigw-idcs-auth-basic/pom.xml b/samples/oci-apigw-idcs-auth-basic/pom.xml index 7c9c92d..635f9a6 100644 --- a/samples/oci-apigw-idcs-auth-basic/pom.xml +++ b/samples/oci-apigw-idcs-auth-basic/pom.xml @@ -1,15 +1,15 @@ - + 4.0.0 UTF-8 1.0.146 + 11 + com.example.fn basicAuth - 1.0.0 + 1.0.1 @@ -49,18 +49,18 @@ maven-compiler-plugin 3.3 - 11 - 11 + ${jdk.version} + ${jdk.version} - org.apache.maven.plugins - maven-surefire-plugin - 2.22.1 - - false - + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + false + - + \ No newline at end of file diff --git a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/BasicAuth.java b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/BasicAuth.java index a25a4f2..2000d13 100644 --- a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/BasicAuth.java +++ b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/BasicAuth.java @@ -1,40 +1,77 @@ package com.example.fn; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; +import static com.example.fn.util.ResourceServerConfig.TOKEN_PREFIX; +import static com.example.fn.util.ResourceServerConfig.CONFIG_KEY_IDCS_URL; +import static com.example.fn.util.ResourceServerConfig.CONFIG_KEY_CLIENT_ID; +import static com.example.fn.util.ResourceServerConfig.CONFIG_KEY_CLIENT_SECRET; +import static com.example.fn.util.ResourceServerConfig.CONFIG_KEY_SCOPE_AUD; +import static com.example.fn.util.ResourceServerConfig.DEFAULT_GRANT_TYPE; +import static com.example.fn.util.ResourceServerConfig.TOKEN_CLAIM_KEY_EXPIRY; +import static com.example.fn.util.ResourceServerConfig.TOKEN_CLAIM_KEY_SUBJECT; +import static com.example.fn.util.ResourceServerConfig.TOKEN_CLAIM_KEY_SCOPE; + import java.net.http.HttpResponse; -import java.net.URI; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.InterruptedException; import org.json.JSONObject; -import java.util.*; -public class BasicAuth { - public static class Input { - public String type; - public String token; - } +import com.example.fn.util.Helper; +import com.fnproject.fn.api.FnConfiguration; +import com.fnproject.fn.api.RuntimeContext; - public static class Result { - public boolean active = false; - public String principal; - public String[] scope; - public String expiresAt; - } +import java.util.*; - private static final String TOKEN_PREFIX = "Basic "; - private static final String IDCS_URL = ResourceServerConfig.IDCS_URL; +/** + * Main class implementing the {@code handleRequest} method, takes the user + * credentials as input, authenticates the credentials against the identity + * provider and if the authentication is successful then returns the following + * claims from the access token
+ *
    + *
  • exp
  • + *
  • sub
  • + *
  • scope
  • + *
+ */ +public class BasicAuth { - public String[] getUserDetailsFromToken(String token) { - String data = token.substring(TOKEN_PREFIX.length()); - String[] user = new String(Base64.getDecoder().decode(data)).split(":", 2); - return user; + private static String IDCS_URL = null; + private static String CLIENT_ID = null; + private static String CLIENT_SECRET = null; + private static String SCOPE_AUD = null; + + /** + * Reads the configuration parameters and sets the respective variables during + * function initialization. + * + * @param {@link RuntimeContext} ctx + */ + @FnConfiguration + public void config(final RuntimeContext ctx) { + IDCS_URL = ctx.getConfigurationByKey(CONFIG_KEY_IDCS_URL).orElse(null); + CLIENT_ID = ctx.getConfigurationByKey(CONFIG_KEY_CLIENT_ID).orElse(null); + CLIENT_SECRET = ctx.getConfigurationByKey(CONFIG_KEY_CLIENT_SECRET).orElse(null); + SCOPE_AUD = ctx.getConfigurationByKey(CONFIG_KEY_SCOPE_AUD).orElse(null); } - public Result handleRequest(Input input) throws UnsupportedEncodingException, InterruptedException { - Result returnValue = new Result(); + /** + * Entry point of the function + * + * @param {@link Input} input + * @return {@link Result} + * @throws UnsupportedEncodingException + * @throws InterruptedException + */ + public Result handleRequest(final Input input) throws UnsupportedEncodingException, InterruptedException { + final Result returnValue = new Result(); + + if (!isConfigValid()) { + returnValue.active = false; + System.out.println( + "Function initialization error, all config parameters not set, please ensure that following config variables are set IDCS_URL, CLIENT_ID, CLIENT_SECRET"); + return returnValue; + } if (input.token == null || !input.token.startsWith(TOKEN_PREFIX)) { returnValue.active = false; @@ -42,50 +79,29 @@ public Result handleRequest(Input input) throws UnsupportedEncodingException, In return returnValue; } - String[] user = getUserDetailsFromToken(input.token); + final String[] user = getUserDetailsFromToken(input.token); if (user.length != 2 || user[0] == null || user[0].isEmpty() || user[1] == null || user[1].isEmpty()) { + returnValue.active = false; System.out.println("Request error username or password missing"); return returnValue; } - String username = user[0]; - String password = user[1]; - String clientId = ResourceServerConfig.CLIENT_ID; - String clientSecret = ResourceServerConfig.CLIENT_SECRET; - String authzHdrVal = clientId + ":" + clientSecret; - String idcsScope = ResourceServerConfig.SCOPE_AUD; - - String reqBody = "grant_type=password" + - "&username=" + username + - "&password=" + password + - "&scope=" + idcsScope; + final String authzHdrVal = CLIENT_ID + ":" + CLIENT_SECRET; + final String reqBody = Helper.createRequestBody(user[0], user[1], SCOPE_AUD, DEFAULT_GRANT_TYPE); try { - HttpClient client = HttpClient.newHttpClient(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(IDCS_URL)) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(authzHdrVal.getBytes("UTF-8"))) - .POST(HttpRequest.BodyPublishers.ofString(reqBody)) - .build(); - - HttpResponse response = client.send( request, - HttpResponse.BodyHandlers.ofString() ); - + final HttpResponse response = Helper.callIDCS(IDCS_URL, authzHdrVal, reqBody); if (response.statusCode() == 200) { + final String responseString = (String) response.body(); + final JSONObject payload = Helper.getTokenBody(responseString); - String responseString = (String) response.body(); - String[] chunks = responseString.split("\\."); - String respjson = new String(Base64.getUrlDecoder().decode(chunks[1])); - JSONObject payload = new JSONObject(respjson); - Date expTime = new Date(payload.getLong("exp")*1000); - - returnValue.principal = payload.getString("sub"); - returnValue.scope = payload.getString("scope").split(" "); + final Date expTime = new Date(payload.getLong(TOKEN_CLAIM_KEY_EXPIRY) * 1000); + returnValue.principal = payload.getString(TOKEN_CLAIM_KEY_SUBJECT); + returnValue.scope = payload.getString(TOKEN_CLAIM_KEY_SCOPE).split(" "); returnValue.expiresAt = expTime.toString(); returnValue.active = true; + System.out.println("Authentication successful"); } } catch (IOException e) { @@ -93,4 +109,45 @@ public Result handleRequest(Input input) throws UnsupportedEncodingException, In } return returnValue; } + + /** + * Returns true if all the configuration variables are set else returns false + * + * @return + */ + private boolean isConfigValid() { + if (IDCS_URL != null && !IDCS_URL.trim().isEmpty() + && CLIENT_ID != null && !CLIENT_ID.trim().isEmpty() + && CLIENT_SECRET != null && !CLIENT_SECRET.isEmpty() + && SCOPE_AUD != null && !SCOPE_AUD.trim().isEmpty()) { + return true; + } + return false; + } + + /** + * The input contains the username and password in {@link Base64} encoded + * format. The format of the token is Base64 encoded :. This + * function decodes the token and returns the username and password. + * + * @param {@link String} token + * @return {@link String} array with username and password + */ + private String[] getUserDetailsFromToken(final String token) { + final String data = token.substring(TOKEN_PREFIX.length()); + final String[] user = new String(Base64.getDecoder().decode(data)).split(":", 2); + return user; + } + + public static class Input { + public String type; + public String token; + } + + public static class Result { + public boolean active = false; + public String principal; + public String[] scope; + public String expiresAt; + } } \ No newline at end of file diff --git a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/ResourceServerConfig.java b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/ResourceServerConfig.java deleted file mode 100644 index f073790..0000000 --- a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/ResourceServerConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.fn; - -public class ResourceServerConfig { - - //YOUR IDENTITY DOMAIN AND APPLICATION CREDENTIALS - public static final String CLIENT_ID = ""; - public static final String CLIENT_SECRET = ""; - public static final String IDCS_URL = ""; - - //INFORMATION ABOUT THE TARGET APPLICATION (For eg : display-httprequest-infodisplay-httprequest-info) - public static final String SCOPE_AUD = ""; - - //TEST CLIENT CREDENTIALS - public static final String TEST_CLIENT_ID = ""; - public static final String TEST_CLIENT_SECRET = ""; - public static final String TEST_CLIENT_SCOPE = ""; - public static final String TOKEN = ""; // EG : "aW5jaGFyvcmFjbGUuY29tFhU" - - - -} \ No newline at end of file diff --git a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/Helper.java b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/Helper.java new file mode 100644 index 0000000..474868d --- /dev/null +++ b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/Helper.java @@ -0,0 +1,84 @@ +package com.example.fn.util; + +import static com.example.fn.util.ResourceServerConfig.TOKEN_PREFIX; +import static com.example.fn.util.ResourceServerConfig.HEADER_NAME_CONTENT_TYPE; +import static com.example.fn.util.ResourceServerConfig.HEADER_NAME_AUTHORIZATION; +import static com.example.fn.util.ResourceServerConfig.HEADER_VALUE_CONTENT_TYPE; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Base64; + +import org.json.JSONObject; + +/** + * Util class that contains some common utility methods + */ +public final class Helper { + + private Helper() { + // + } + + /** + * Creates the POST request body that will be sent to IDCS token endpoint + * + * @param {@link String} username + * @param {@link String} password + * @param {@link String} scope + * @param {@link String} grantType + * @return {@link String} request body + */ + public static String createRequestBody(final String username, final String password, final String scope, + final String grantType) { + return "grant_type=" + grantType + + "&username=" + username + + "&password=" + password + + "&scope=" + scope; + } + + /** + * + * Calls IDCS token endpoint to validate the username and password and get the + * access token. + * + * @param {@link String} url + * @param {@link String} authHeader + * @param {@link String} requestBody + * @return {@link HttpResponse} + * @throws InterruptedException + * @throws IOException + */ + public static HttpResponse callIDCS(final String url, final String authHeader, final String requestBody) + throws IOException, InterruptedException { + final HttpClient client = HttpClient.newHttpClient(); + + final HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header(HEADER_NAME_CONTENT_TYPE, HEADER_VALUE_CONTENT_TYPE) + .header(HEADER_NAME_AUTHORIZATION, + TOKEN_PREFIX + Base64.getEncoder().encodeToString(authHeader.getBytes("UTF-8"))) + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + return client.send(request, + HttpResponse.BodyHandlers.ofString()); + } + + /** + * Returns the body from the access token. The token is of the JWT format with 3 + * parts separated by "." First part is header, body is the second part and the + * last part is signature + * + * @param response + * @return + */ + public static JSONObject getTokenBody(final String response) { + final String[] chunks = response.split("\\."); + final String respjson = new String(Base64.getUrlDecoder().decode(chunks[1])); + return new JSONObject(respjson); + } +} \ No newline at end of file diff --git a/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/ResourceServerConfig.java b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/ResourceServerConfig.java new file mode 100644 index 0000000..ba6e0d6 --- /dev/null +++ b/samples/oci-apigw-idcs-auth-basic/src/main/java/com/example/fn/util/ResourceServerConfig.java @@ -0,0 +1,30 @@ +package com.example.fn.util; + +/** + * Util class that contains the constants used across the function + */ +public final class ResourceServerConfig { + + private ResourceServerConfig() { + // + } + + public static final String TOKEN_PREFIX = "Basic "; + + public static final String CONFIG_KEY_IDCS_URL = "IDCS_URL"; + public static final String CONFIG_KEY_CLIENT_ID = "CLIENT_ID"; + public static final String CONFIG_KEY_CLIENT_SECRET = "CLIENT_SECRET"; + public static final String CONFIG_KEY_SCOPE_AUD = "SCOPE_AUD"; + + public static final String DEFAULT_GRANT_TYPE = "password"; + + public static final String HEADER_NAME_CONTENT_TYPE = "Content-Type"; + public static final String HEADER_NAME_AUTHORIZATION = "Authorization"; + public static final String HEADER_VALUE_CONTENT_TYPE = "application/x-www-form-urlencoded"; + + public static final Integer HTTP_RESPONSE_OK = 200; + + public static final String TOKEN_CLAIM_KEY_EXPIRY = "exp"; + public static final String TOKEN_CLAIM_KEY_SUBJECT = "sub"; + public static final String TOKEN_CLAIM_KEY_SCOPE = "scope"; +} \ No newline at end of file diff --git a/samples/oci-apigw-idcs-auth-basic/src/test/java/com/example/fn/BasicAuthTest.java b/samples/oci-apigw-idcs-auth-basic/src/test/java/com/example/fn/BasicAuthTest.java index e2a1096..3f14b6e 100644 --- a/samples/oci-apigw-idcs-auth-basic/src/test/java/com/example/fn/BasicAuthTest.java +++ b/samples/oci-apigw-idcs-auth-basic/src/test/java/com/example/fn/BasicAuthTest.java @@ -1,5 +1,6 @@ package com.example.fn; +import com.example.fn.util.ResourceServerConfig; import com.fasterxml.jackson.databind.ObjectMapper; import com.fnproject.fn.testing.*; import org.junit.*; @@ -7,28 +8,31 @@ import static org.junit.Assert.*; import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Base64; public class BasicAuthTest { + // set these variables with the test env values + private static final String TEST_IDCS_URL = ""; + private static final String TEST_CLIENT_ID = ""; + private static final String TEST_CLIENT_SECRET = ""; + private static final String TEST_SCOPE_AUD = ""; + private static final String TEST_TOKEN = ""; + + private static String INPUT_FORMAT = "{\n" + + " \"type\":\"TOKEN\",\n" + + " \"token\": \"Basic %s\"\n" + + "}"; + private static String INVALID_TOKEN = "Basic "; + private static final ObjectMapper mapper = new ObjectMapper(); + public static class Result { - + public boolean active; public String principal; public String[] scope; public String expiresAt; } - private static final String IDCS_URL = ResourceServerConfig.IDCS_URL; - private static String INPUT_FORMAT = "{\n" + - " \"type\":\"TOKEN\",\n" + - " \"token\": \"Basic %s\"\n" + - "}"; - private static String INVALID_TOKEN = "Basic "; @Rule public final FnTestingRule testing = FnTestingRule.createDefault(); @@ -36,62 +40,39 @@ public static class Result { @Test public void shouldReturnInactive() throws IOException { final String input = "{\n" + - " \"type\":\"TOKEN\",\n" + - " \"token\": \"" + INVALID_TOKEN + "\"\n" + - "}"; + " \"type\":\"TOKEN\",\n" + + " \"token\": \"" + INVALID_TOKEN + "\"\n" + + "}"; - testing.givenEvent().withBody(input).enqueue(); - testing.thenRun(BasicAuth.class, "handleRequest"); + testing.givenEvent().withBody(input).enqueue(); + testing.thenRun(BasicAuth.class, "handleRequest"); - FnResult fnResult = testing.getOnlyResult(); + FnResult fnResult = testing.getOnlyResult(); - Result result = mapper.readValue(fnResult.getBodyAsString(), Result.class); - assertFalse(result.active); + Result result = mapper.readValue(fnResult.getBodyAsString(), Result.class); + assertFalse(result.active); } @Test public void shouldReturnActive() throws Exception { + testing.setConfig(ResourceServerConfig.CONFIG_KEY_CLIENT_ID, TEST_CLIENT_ID); + testing.setConfig(ResourceServerConfig.CONFIG_KEY_CLIENT_SECRET, TEST_CLIENT_SECRET); + testing.setConfig(ResourceServerConfig.CONFIG_KEY_IDCS_URL, TEST_IDCS_URL); + testing.setConfig(ResourceServerConfig.CONFIG_KEY_SCOPE_AUD, TEST_SCOPE_AUD); - String token = ResourceServerConfig.TOKEN; - - final String input = String.format(INPUT_FORMAT, token); - String[] user = new String(Base64.getDecoder().decode(token)).split(":", 2); - String username = user[0]; - String password = user[1]; - String clientId = ResourceServerConfig.CLIENT_ID; - String clientSecret = ResourceServerConfig.CLIENT_SECRET; - String authzHdrVal = clientId + ":" + clientSecret; - String idcsScope = ResourceServerConfig.SCOPE_AUD; - - String reqBody = "grant_type=password" + - "&username=" + username + - "&password=" + password + - "&scope=" + idcsScope; + final String input = String.format(INPUT_FORMAT, TEST_TOKEN); try { - HttpClient client = HttpClient.newHttpClient(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(IDCS_URL)) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(authzHdrVal.getBytes("UTF-8"))) - .POST(HttpRequest.BodyPublishers.ofString(reqBody)) - .build(); - - HttpResponse response = client.send( request, - HttpResponse.BodyHandlers.ofString() ); - System.out.println(response.statusCode()); - - testing.givenEvent().withBody(input).enqueue(); - testing.thenRun(BasicAuth.class, "handleRequest"); + testing.givenEvent().withBody(input).enqueue(); + testing.thenRun(BasicAuth.class, "handleRequest"); - FnResult fnResult = testing.getOnlyResult(); + FnResult fnResult = testing.getOnlyResult(); - Result result = mapper.readValue(fnResult.getBodyAsString(), Result.class); - assertTrue(result.active); - System.out.println(fnResult.getBodyAsString()); + Result result = mapper.readValue(fnResult.getBodyAsString(), Result.class); + assertTrue(result.active); + System.out.println(fnResult.getBodyAsString()); + } finally { + // do nothing } - finally{} } - } \ No newline at end of file