diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..80bfbd5 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +name: Build + +on: + push: + branches: + - master + - dev + - dev-* + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - name: Notify slack success + if: success() + id: slack # IMPORTANT: reference this step ID value in future Slack steps + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + channel: github-actions + status: STARTING + color: warning + + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Build + run: mvn -B package --file pom.xml + + - name: Run Tests + run: mvn test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true + + - name: Notify slack success + if: success() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: SUCCESS + color: good + + - name: Notify slack fail + if: failure() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: FAILED + color: danger diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..395c068 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + pull_request: + branches: + - master + - dev + - dev-* + +jobs: + ci: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - name: Notify slack success + if: success() + id: slack # IMPORTANT: reference this step ID value in future Slack steps + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + channel: github-actions + status: STARTING + color: warning + + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Build + run: mvn -B package --file pom.xml + + - name: Run Tests + run: mvn test + + - name: Notify slack success + if: success() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: SUCCESS + color: good + + - name: Notify slack fail + if: failure() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: FAILED + color: danger diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..cea2dba --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: Publish + +on: + release: + types: [created] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Notify Starting + if: success() + id: slack # IMPORTANT: reference this step ID value in future Slack steps + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + channel: github-actions + status: STARTING + color: warning + + - uses: actions/checkout@v2 + - name: Set up Maven Central Repository + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Build + run: mvn -B package --file pom.xml + + - name: Run Tests + run: mvn test + + - name: Release Maven package + uses: samuelmeuli/action-maven-publish@v1 + with: + gpg_private_key: ${{ secrets.gpg_private_key }} + gpg_passphrase: ${{ secrets.gpg_passphrase }} + nexus_username: ${{ secrets.nexus_username }} + nexus_password: ${{ secrets.nexus_password }} + + - name: Notify slack success + if: success() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: SUCCESS + color: good + + - name: Notify slack fail + if: failure() + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + uses: voxmedia/github-action-slack-notify-build@v1.1.1 + with: + message_id: ${{ steps.slack.outputs.message_id }} + channel: github-actions + status: FAILED + color: danger \ No newline at end of file diff --git a/.gitignore b/.gitignore index 146a7f2..7cd28f6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,11 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +/target +/.classpath +/.project +*.iml +.idea +.vscode +.DS_Store diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1b6e1ef --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/LICENSE b/LICENSE index 7f4ad0a..edac868 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 nevoalm +Copyright (c) 2019 SecureNative Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 6c3e32c..ff6e4ee 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,310 @@ - -# Java SDK for SecureNative - - -**[SecureNative](https://www.securenative.com/) is rethinking-security-as-a-service, disrupting the cyber security space and the way enterprises consume and implement security solutions.** - - -#SDK - -This Java sdk is very light, comes with only two dependencies (httpAsyncClient and Jackson). -In addition, you can find two modules that can help you: - -[Spring](https://github.com/securenative/securenative-java/tree/master/spring) or any web application that uses javax.servlet - -[akka-http](https://github.com/securenative/securenative-java/tree/master/akka-http) - -# Quickstart +

+ SecureNative Logo +

+ +

+ A Cloud-Native Security Monitoring and Protection for Modern Applications +

+

+ + Github Actions + + + + + + npm version + +

+

+ Documentation | + Quick Start | + Blog | + Chat with us on Slack! +

+
+ + +[SecureNative](https://www.securenative.com/) performs user monitoring by analyzing user interactions with your application and various factors such as network, devices, locations and access patterns to stop and prevent account takeover attacks. + +## Install the SDK When using Maven, add the following dependency to your `pom.xml` file: ```xml - - com.securenative.java - sdk-base - 0.2.4 - + + com.securenative.java + securenative-java + LATEST + ``` -Gradle: - -compile group: 'com.securenative.java', name: 'sdk-base', version: 'LATEST' - +When using Gradle, add the following dependency to your `build.gradle` file: +```gradle +compile group: 'com.securenative.java', name: 'sdk-parent', version: '0.3.1', ext: 'pom' +``` +When using SBT, add the following dependency to your `build.sbt` file: +```sbt +libraryDependencies += "com.securenative.java" % "sdk-parent" % "0.3.1" pomOnly() +``` ## Initialize the SDK -Go to the settings page of your SecureNative account and find your **API KEY** +To get your *API KEY*, login to your SecureNative account and go to project settings page: -**Initialize using API KEY** +### Option 1: Initialize via Config file +SecureNative can automatically load your config from *securenative.properties* file or from the file that is specified in your *SECURENATIVE_CONFIG_FILE* env variable: ```java - secureNative = new SecureNative(API_KEY,new SecureNativeOptions()); +// Options 1: Use default config file path +try { + SecureNative securenative = SecureNative.init(); +} catch (SecureNativeSDKException | SecureNativeConfigException e) { + e.printStackTrace(); +} + +// Options 2: Use specific config file path +Path path = Paths.get("/path/to/securenative.properties"); +try { + SecureNative.init(path); +} catch (SecureNativeSDKException | SecureNativeConfigException e) { + System.err.printf("Could not initialize SecureNative sdk; %s%n", e); +} ``` +### Option 2: Initialize via API Key -You can pass empty SecureNativeOptions object or you can set the following: +```java +try { + SecureNative securenative = SecureNative.init("YOUR_API_KEY"); +} catch (SecureNativeSDKException | SecureNativeConfigException e) { + e.printStackTrace(); +} +``` - api url - target url the events will be sent (https://api.securenative.com/collector/api/v1). - interval - minimum interval between sending events (1000ms). - max events - maximum events that will be sent (1000). - timeout - (1500 ms). +### Option 3: Initialize via ConfigurationBuilder +```java +try { + securenative = SecureNative.init(SecureNative.configBuilder() + .withApiKey("API_KEY") + .withMaxEvents(10) + .withLogLevel("error") + .build()); +} catch (SecureNativeSDKException e) { + e.printStackTrace(); +} +``` - ```java - secureNative = new SecureNative(API_KEY,new SecureNativeOptions( - "https://other.domain.com/collector/api/v1", - 1200, - 5000, - 2000 - )); - ``` +## Getting SecureNative instance +Once initialized, sdk will create a singleton instance which you can get: +```java +SecureNative securenative = null; +try { + securenative = SecureNative.getInstance(); +} catch (SecureNativeSDKIllegalStateException e) { + System.err.printf("Could not get SecureNative instance; %s%n", e); +} +``` ## Tracking events -Once the SDK has been initialized, tracking requests are sent through the SDK +Once the SDK has been initialized, tracking requests sent through the SDK instance. Make sure you build event with the EventBuilder: ```java -Event event = new SnEvent.EventBuilder(EventTypes.LOG_IN.getType()). - withUser(new User("","","apple@sucks.com")). - withIp("35.199.23.1"). - withCookieValue("eyJjaWQiOiJkYzgyYjdhZS00ODFkLTQyODItYTMyZC0xZTU1Njk2ZjNmZTQiLCJmcCI6Ijk5NGYzZjVjZTRiYWUwODQzMTRhOTFkNzgyN2I1MWYuMjQ3MDBmOWYxOTg2ODAwYWI0ZmNjODgwNTMwZGQwZWQifQ"). - withRemoteIP("35.199.23.1"). - withUserAgent("Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36"). - build(); +@RequestMapping("/track") +public void track() { + SecureNative securenative = null; + try { + securenative = SecureNative.getInstance(); + } catch (SecureNativeSDKIllegalStateException e) { + System.err.printf("Could not get SecureNative instance; %s%n", e); + } + + SecureNativeContext context = SecureNative.contextBuilder() + .withIp("37.86.255.94") + .withClientToken("SECURENATIVE_CLIENT_TOKEN") + .withHeaders(Maps.defaultBuilder() + .put("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36") + .build()) + .build(); + + EventOptions eventOptions = null; + try { + eventOptions = EventOptionsBuilder.builder(EventTypes.LOG_IN) + .userId("1234") + .userTraits("Your Name", "name@gmail.com", "+1234567890") + .context(context) + .properties(Maps.builder() + .put("prop1", "CUSTOM_PARAM_VALUE") + .put("prop2", true) + .put("prop3", 3) + .build()) + .timestamp(new Date()) + .build(); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); + } + + try { + securenative.track(eventOptions); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); + } +} ``` -**Example** +You can also create request context from HttpServletRequest: ```java - @RequestMapping("/track") - public String track( HttpServletRequest request, HttpServletResponse response) { - try { - secureNative = new SecureNative(API_KEY,new SecureNativeOptions()); - Event event = new SnEvent.EventBuilder(EventTypes.LOG_IN.getType()). - withUser(new User("","","chuck@norris.com")). - withIp("35.199.23.1"). - withCookieValue("eyJjaWQiOiJkYzgyYjdhZS00ODFkLTQyODItYTMyZC0xZTU1Njk2ZjNmZTQiLCJmcCI6Ijk5NGYzZjVjZTRiYWUwODQzMTRhOTFkNzgyN2I1MWYuMjQ3MDBmOWYxOTg2ODAwYWI0ZmNjODgwNTMwZGQwZWQifQ"). - withRemoteIP("35.199.23.1"). - withUserAgent("Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 QQ-URL-Manager Mobile Safari/537.36"). - build(); - secureNative.track(event); - - - } catch (SecureNativeSDKException e) { - e.printStackTrace(); - return "Api key is not valid"; - } - return "tracked"; +@RequestMapping("/track") +public void track(HttpServletRequest request, HttpServletResponse response) { + SecureNative securenative = null; + try { + securenative = SecureNative.getInstance(); + } catch (SecureNativeSDKIllegalStateException e) { + System.err.printf("Could not get SecureNative instance; %s%n", e); + } + + SecureNativeContext context = securenative.fromHttpServletRequest(request).build(); + + EventOptions eventOptions = null; + try { + eventOptions = EventOptionsBuilder.builder(EventTypes.LOG_IN) + .userId("1234") + .userTraits("Your Name", "name@gmail.com", "+1234567890") + .context(context) + .properties(Maps.builder() + .put("prop1", "CUSTOM_PARAM_VALUE") + .put("prop2", true) + .put("prop3", 3) + .build()) + .timestamp(new Date()) + .build(); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); } + try { + securenative.track(eventOptions); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); + } +} ``` -You can build an event from HttpServletRequest or from combination between event and HttpServletRequest: +## Verify events +**Example** ```java - @RequestMapping("/track") - public String track( HttpServletRequest request, HttpServletResponse response) { - try { - secureNative = new SecureNative(API_KEY,new SecureNativeOptions()); - Event e = new SnEvent.EventBuilder(EventTypes.LOG_IN.getType()). - withUser(new User("","","chuck@norris.com")). - build(); - Event event = secureNative.buildEventFromHttpServletRequest(request, e); - secureNative.track(event); - - - } catch (SecureNativeSDKException e) { - e.printStackTrace(); - return "Api key is not valid"; - } - return "tracked"; +@RequestMapping("/verify") +public void verify(HttpServletRequest request, HttpServletResponse response) { +SecureNative securenative = null; + try { + securenative = SecureNative.getInstance(); + } catch (SecureNativeSDKIllegalStateException e) { + System.err.printf("Could not get SecureNative instance; %s%n", e); } + SecureNativeContext context = securenative.fromHttpServletRequest(request).build(); + + EventOptions eventOptions = null; + try { + eventOptions = EventOptionsBuilder.builder(EventTypes.LOG_IN) + .userId("1234") + .userTraits("Your Name", "name@gmail.com", "+1234567890") + .context(context) + .properties(Maps.builder() + .put("prop1", "CUSTOM_PARAM_VALUE") + .put("prop2", true) + .put("prop3", 3) + .build()) + .timestamp(new Date()) + .build(); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); + } + + VerifyResult verifyResult = null; + try { + verifyResult = securenative.verify(eventOptions); + } catch (SecureNativeInvalidOptionsException e) { + e.printStackTrace(); + } + + verifyResult.getRiskLevel(); // Low, Medium, High + verifyResult.getScore(); // Risk score: 0 -1 (0 - Very Low, 1 - Very High) + verifyResult.getTriggers(); // ["TOR", "New IP", "New City"] +} ``` +## Webhook signature verification + +Apply our filter to verify the request is from us, example in spring: + +```java +@RequestMapping("/webhook") +public void webhookEndpoint(HttpServletRequest request, HttpServletResponse response) { + SecureNative securenative = null; + try { + securenative = SecureNative.getInstance(); + } catch (SecureNativeSDKIllegalStateException e) { + System.err.printf("Could not get SecureNative instance; %s%n", e); + } + + // Checks if request is verified + Boolean isVerified = securenative.verifyRequestPayload(request); +} + ``` +## Extract proxy headers from cloud providers +You can specify custom header keys to allow extraction of client ip from different providers. +This example demonstrates the usage of proxy headers for ip extraction from Cloudflare. +### Option 1: Using config file +```properties +SECURENATIVE_API_KEY="YOUR_API_KEY" +SECURENATIVE_PROXY_HEADERS=["CF-Connecting-IP"] +``` -## Verification events +Initialize sdk as shown above. -**Example** +### Options 2: Using ConfigurationBuilder ```java - @RequestMapping("/verify") - public String verify(HttpServletRequest request, HttpServletResponse response) { - try { - secureNative = new SecureNative(API_KEY,new SecureNativeOptions()); - } catch (SecureNativeSDKException e) { - e.printStackTrace(); - return "Api key is not valid"; - } - secureNative.verify(new SnEvent.EventBuilder(EventTypes.LOG_IN.getType()).withUser(new User("1","Dan","Dan@Dan.dan")).withIp(ip).withRemoteIP(remoteIP).withUserAgent(userAgent).build()); -); - return "verify"; - } - +try { + securenative = SecureNative.init(SecureNative.configBuilder() + .withApiKey("API_KEY") + .WithProxyHeaders(new ["CF-Connecting-IP"]) + .build()); +} catch (SecureNativeSDKException e) { + e.printStackTrace(); +} ``` -## Flow events -**Example** +## Remove PII Data From Headers -```java - @RequestMapping("/flow") - public String flow( HttpServletRequest request, HttpServletResponse response) { - try { - secureNative = new SecureNative(API_KEY,new SecureNativeOptions()); - } catch (SecureNativeSDKException e) { - e.printStackTrace(); - return "Api key is not valid"; - } - secureNative.flow(1,new SnEvent.EventBuilder(EventTypes.LOG_IN.getType()).withUser(new User("1","Dan","Dan@Dan.dan")).withIp(ip).withRemoteIP(remoteIP).withUserAgent(userAgent).build()); - return "flow"; - } +By default, SecureNative SDK remove any known pii headers from the received request. +We also support using custom pii headers and regex matching via configuration, for example: + +### Option 1: Using config file +```properties +SECURENATIVE_API_KEY="YOUR_API_KEY" +SECURENATIVE_PII_HEADERS=["apiKey"] ``` -## Webhook entry filter +Initialize sdk as shown above. -Apply our filter to verify the request is from us, example in spring: +### Options 2: Using ConfigurationBuilder ```java - - @Bean - public FilterRegistrationBean filterWebhook() throws SecureNativeSDKException { - FilterRegistrationBean registrationBean = new FilterRegistrationBean(); - VerifyWebHookMiddleware customURLFilter = new VerifyWebHookMiddleware("API KEY"); - registrationBean.setFilter(customURLFilter); - return registrationBean; - } - ``` \ No newline at end of file +try { + securenative = SecureNative.init(SecureNative.configBuilder() + .withApiKey("API_KEY") + .WithPiiRegexPattern("((?i)(http_auth_)(\\w+)?)") + .build()); +} catch (SecureNativeSDKException e) { + e.printStackTrace(); +} +``` \ No newline at end of file diff --git a/akka-http/README.md b/akka-http/README.md deleted file mode 100644 index b162bf1..0000000 --- a/akka-http/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# Akka Http for Secure Native -This module adds Akka Http (scala) directives to Secure Native SDK in order to easily integrate with Akka Http applications. - -## Quickstart -Download the latest version of the SDK to your favourite scala build system: - -#### Maven -``` - - com.securenative.java - akka-http - LATEST - -``` - -#### Gradle -`compile group: 'com.securenative.java', name: 'akka-http', version: 'LATEST'` - -#### SBT -`libraryDependencies += "com.securenative.java" % "akka-http" % "LATEST"` - -### Initialize the SDK -Create an instance (singleton) of the SecureNative object with your Api Key: -```scala -import com.securenative.snlogic.SecureNative -import com.securenative.models.SecureNativeOptions - -object Main { - val apiKey = "YOUR_API_KEY" - implicit val snSdk: SecureNative = new SecureNative(apiKey, new SecureNativeOptions()) -} -``` - -### Tracking Events -Secure Native let you send Async event that won't impact your regular flows, you may use our built in types (such as login, logout, etc...) -or using your own custom events. Secure Native needs these events in order to learn the user behaviour. -```scala -object Main { - val apiKey = "YOUR_API_KEY" - implicit val snSdk: SecureNative = new SecureNative(apiKey, new SecureNativeOptions()) - - def main(args: Array[String]): Unit = { - - def route = path("login") { - post { - // Add Secure Native event directive to automatically build your event from - // the http request: - SnDirectives.extractSnEvent(EventTypes.LOG_IN)(snSdk) { builder => - - /* YOUR LOGIN BUSINESS LOGIC */ - - val event = builder // You may change or add your custom fields to the builder - .withUser(new User("jas723h2", "Jack Jefferson", "jackje@gmail.com")) - .build() // Finally call the build method to create the event - - // Track the event using the SDK: - snSdk.track(event) - complete("SOMETHING") - } - } - } - } -} -``` - - -### Guarding Sensitive Operations -In order to guard sensitive resources you can use the `verify` method, which will return the risk score of the current user. -Allowing you to take a decision on how to act, for example when trying to delete project from github we can use the verify method before actually deleting the project -when getting the risk score you can decide if you want to allow/block the deletion. - -```scala -object Main { - val apiKey = "YOUR_API_KEY" - implicit val snSdk: SecureNative = new SecureNative(apiKey, new SecureNativeOptions()) - - def main(args: Array[String]): Unit = { - - def route = path("project") { - delete { - SnDirectives.extractSnEvent(EventTypes.LOG_IN)(snSdk) { builder => - val event = builder - .withUser(new User("jas723h2", "Jack Jefferson", "jackje@gmail.com")) - .build() // Finally call the build method to create the event - - val riskScore = snSdk.verify(event) - - if (riskScore.riskLevel == "high") { - /* Your blocking logic */ - } else { - /* Your allowing logic */ - }s - complete("SOMETHING") - } - } - } - } -} -``` - -### Accepting Webhooks from Secure Native -Secure Native can respond on real time to changes in risk score based on your sent events to get those real-time notification -you can create a new endpoint so we can call it when something happens. -In order to verify that the incoming request originated from Secure Native servers we have another directive that called `verifyWebhook`. -```scala -object Main { - val apiKey = "YOUR_API_KEY" - def main(args: Array[String]): Unit = { - - def route = path("riskscore") { - post { // it has to be a post endpoint - SnDirectives.verifyWebhook(apiKey) { body => - println(body) // the body will be passed only for Secure Native requests - complete(body) - } - } - } - } -} -``` \ No newline at end of file diff --git a/akka-http/pom.xml b/akka-http/pom.xml deleted file mode 100644 index 9986d1f..0000000 --- a/akka-http/pom.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - sdk-parent - com.securenative.java - 0.3.0 - - 4.0.0 - - akka - jar - - - 1.6 - 1.6 - 2.15.2 - 2.12 - 10.0.0 - 2.5.13 - - - - - org.scala-lang - scala-library - ${scala-library.version}.6 - - - - com.securenative.java - sdk-base - 0.2.0 - - - - com.typesafe.akka - akka-http_${scala-library.version} - ${akka-http.version} - provided - - - - com.typesafe.akka - akka-stream_${scala-library.version} - ${akka.version} - provided - - - - - - - net.alchim31.maven - scala-maven-plugin - 3.2.2 - - - - compile - - - - attach-javadocs - - doc-jar - - - - attach-sources - - add-source - - - - - - maven-assembly-plugin - - - - your.MainClass - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - \ No newline at end of file diff --git a/akka-http/src/main/scala/com/securenative/java/akka/SnDirectives.scala b/akka-http/src/main/scala/com/securenative/java/akka/SnDirectives.scala deleted file mode 100644 index b5809f3..0000000 --- a/akka-http/src/main/scala/com/securenative/java/akka/SnDirectives.scala +++ /dev/null @@ -1,54 +0,0 @@ -package com.securenative.java.akka - -import akka.http.scaladsl.model.HttpRequest -import akka.http.scaladsl.server.directives.{BasicDirectives, MarshallingDirectives} -import akka.http.scaladsl.server.{Directive1, ValidationRejection} -import com.securenative.models.SnEvent.EventBuilder -import com.securenative.models.{EventTypes, SnEvent} -import com.securenative.snlogic.{SecureNative, Utils} - -object SnDirectives extends BasicDirectives with MarshallingDirectives { - private val DEFAULT_IP = "127.0.0.1" - private val utils = new Utils() - - def verifyWebhook(secret: String): Directive1[String] = extractRequest.flatMap { request => - entity(as[String]).flatMap { body => - val headerSig = request.getHeader(utils.SN_HEADER) - - if (!headerSig.isPresent) { - cancelRejection(ValidationRejection(s"Failed to extract ${utils.SN_HEADER} header from the request, this request cannot be validated and probably not sent from SecureNative.")) - } - - if (utils.isVerifiedSnRequest(body, headerSig.get().value(), secret)) { - provide(body) - } else { - provide(null) - } - } - } - - def extractSnEvent(eventType: EventTypes)(implicit sdk: SecureNative): Directive1[EventBuilder] = extractRequest.flatMap { request => - val builder = fromRequest(eventType, request) - provide(builder) - } - - private def fromRequest(eventName: EventTypes, httpRequest: HttpRequest)(implicit snSdk: SecureNative): SnEvent.EventBuilder = { - val headers = Map(httpRequest.headers.map { h => h.name.toLowerCase -> h.value }: _*) - val cookies = Map(httpRequest.cookies.map { c => c.name -> c.value }: _*) - - val ua = headers.getOrElse("user-agent", "") - - val utils = new Utils() - val ip = utils.remoteIpFromRequest(name => headers.getOrElse(name, DEFAULT_IP)) - val remoteIp = headers.getOrElse("remote-address", DEFAULT_IP) - val snCookie = cookies.getOrElse(snSdk.getDefaultCookieName, headers.getOrElse(utils.SN_HEADER, "")) - - val builder = new SnEvent.EventBuilder(eventName.getType) - .withCookieValue(snCookie) - .withIp(ip) - .withRemoteIP(remoteIp) - .withUserAgent(ua) - - builder - } -} diff --git a/pom.xml b/pom.xml index 4ce04af..61a01ee 100644 --- a/pom.xml +++ b/pom.xml @@ -1,33 +1,26 @@ - - - 4.0.0 + + + 4.0.0 com.securenative.java - sdk-parent - 0.3.0 + securenative-java + jar + 0.5.7 https://github.com/securenative/securenative-java + + ${project.groupId}:${project.artifactId}:${project.version} + SecureNative user monitoring sdk. + The MIT License https://opensource.org/licenses/MIT - - - Nevo Elmalem - nevo@securenative.com - securenative - http://www.securenative.com - - - - scm:git:git://github.com/securenative/securenative-java.git - scm:git:ssh://github.com:securenative/securenative-java.git - http://github.com/securenative/securenative-java/tree/master - + + ossrh https://oss.sonatype.org/content/repositories/snapshots @@ -37,6 +30,161 @@ https://oss.sonatype.org/service/local/staging/deploy/maven2 + + + + SecureNative Dev Team + info@securenative.com + SecureNative + https://www.securenative.com + + + + + scm:git:git://github.com/securenative/securenative-java.git + scm:git:ssh://github.com:securenative/securenative-java.git + http://github.com/securenative/securenative-java/tree/master + + + + + deploy + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar-no-fork + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + --pinentry-mode + loopback + + + + + + + + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.11.0 + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + org.apache.maven + maven-model + 3.0.2 + + + org.slf4j + slf4j-api + 1.7.12 + + + com.squareup.okhttp3 + okhttp + 3.14.8 + + + org.mockito + mockito-core + 2.21.0 + test + + + org.springframework + spring-test + 5.2.6.RELEASE + test + + + org.assertj + assertj-core + 3.15.0 + test + + + com.squareup.okhttp3 + mockwebserver + 3.14.8 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.7.0-M1 + test + + + org.junit.jupiter + junit-jupiter-api + 5.7.0-M1 + test + + + org.junit-pioneer + junit-pioneer + 0.5.1 + test + + + org.skyscreamer + jsonassert + 1.5.0 + test + + + @@ -46,6 +194,20 @@ 8 8 + 3.8.1 + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + maven-deploy-plugin @@ -63,7 +225,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.8 true ossrh @@ -73,80 +235,74 @@ org.apache.maven.plugins - maven-source-plugin - 3.0.1 + maven-gpg-plugin + 1.6 - attach-sources + sign-artifacts + verify - jar + sign org.apache.maven.plugins - maven-javadoc-plugin + maven-dependency-plugin - attach-javadocs + copy-dependencies + prepare-package - jar + copy-dependencies + + ${project.build.directory}/lib + false + false + true + org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - maven-assembly-plugin + maven-surefire-plugin + 3.0.0-M3 - - - your.MainClass - - - - jar-with-dependencies - + false + true + false - - - make-assembly - package - - single - - - + + org.jacoco + jacoco-maven-plugin + 0.8.3 + + + + prepare-agent + + + + report + test + + report + + + + - - sdk-base - spring - akka-http - - pom - + UTF-8 + UTF-8 1.6 1.6 0.2.3 - - - \ No newline at end of file + diff --git a/sdk-base/pom.xml b/sdk-base/pom.xml deleted file mode 100644 index 57c0248..0000000 --- a/sdk-base/pom.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - sdk-parent - com.securenative.java - 0.3.0 - - sdk-base - 4.0.0 - - ${project.artifactId} - Secure Native SDK in java - https://github.com/securenative/securenative-java - - - The MIT License - https://opensource.org/licenses/MIT - - - - - Nevo Elmalem - nevo@securenative.com - securenative - http://www.securenative.com - - - - scm:git:git://github.com/securenative/securenative-java.git - scm:git:ssh://github.com:securenative/securenative-java.git - http://github.com/securenative/securenative-java/tree/master - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - - - maven-deploy-plugin - 2.8.2 - - - default-deploy - deploy - - deploy - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - maven-assembly-plugin - - - - your.MainClass - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - - - com.fasterxml.jackson.core - jackson-databind - 2.9.9.2 - - - junit - junit - 4.12 - test - - - org.mockito - mockito-core - 2.21.0 - test - - - org.apache.maven - maven-model - 3.0.2 - - - org.asynchttpclient - async-http-client - 2.2.0 - - - net.bytebuddy - byte-buddy-agent - 1.10.1 - compile - - - org.reflections - reflections - 0.9.11 - - - net.bytebuddy - byte-buddy - 1.9.16 - - - org.apache.logging.log4j - log4j-api - 2.11.2 - - - - \ No newline at end of file diff --git a/sdk-base/src/main/java/com/securenative/models/ActionType.java b/sdk-base/src/main/java/com/securenative/models/ActionType.java deleted file mode 100644 index 122f564..0000000 --- a/sdk-base/src/main/java/com/securenative/models/ActionType.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.securenative.models; - -public enum ActionType { - ALLOW, - BLOCK, - REDIRECT, - MFA -} diff --git a/sdk-base/src/main/java/com/securenative/models/Event.java b/sdk-base/src/main/java/com/securenative/models/Event.java deleted file mode 100644 index 4bc99b8..0000000 --- a/sdk-base/src/main/java/com/securenative/models/Event.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.securenative.models; - -import java.util.Map; - -public interface Event { - String getEventType(); - String getCid(); - String getVid(); - String getFp(); - String getIp(); - String getRemoteIP(); - String getUserAgent(); - User getUser(); - Device getDevice(); - String getCookieName(); - String getCookieValue(); - Map getParams(); -} diff --git a/sdk-base/src/main/java/com/securenative/models/EventOptions.java b/sdk-base/src/main/java/com/securenative/models/EventOptions.java deleted file mode 100644 index 8d98417..0000000 --- a/sdk-base/src/main/java/com/securenative/models/EventOptions.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.securenative.models; - -import java.util.AbstractMap; -import java.util.List; - -public class EventOptions { - private String ip; - private String userAgent; - private String remoteIP; - private User user; - private Device device; - private String cookieName; - private String cookieValue; - private String eventType; - private List > params; - - public EventOptions(String ip, String remoteIP, String userAgent, Device device, User user, String cookieName, String cookieValue, String eventType, List> params) { - this.ip = ip; - this.remoteIP = remoteIP; - this.userAgent = userAgent; - this.device = device; - this.user = user; - this.cookieName = cookieName; - this.eventType = eventType; - this.params = params; - this.cookieValue = cookieValue; - } - - public EventOptions(String ip, String userAgent,String eventType) { - this.ip = ip; - this.userAgent = userAgent; - this.eventType = eventType; - } - - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getRemoteIP() { - return remoteIP; - } - - public void setRemoteIP(String remoteIP) { - this.remoteIP = remoteIP; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public Device getDevice() { - return device; - } - - public void setDevice(Device device) { - this.device = device; - } - - public String getCookieName() { - return cookieName; - } - - public void setCookieName(String cookieName) { - this.cookieName = cookieName; - } - public String getEventType() { - return eventType; - } - - public void setEventType(String eventType) { - this.eventType = eventType; - } - - public List> getParams() { - return params; - } - - public void setParams(List> params) { - this.params = params; - } - - public String getCookieValue() { - return cookieValue; - } - - public void setCookieValue(String cookieValue) { - this.cookieValue = cookieValue; - } - - - - - -} diff --git a/sdk-base/src/main/java/com/securenative/models/Message.java b/sdk-base/src/main/java/com/securenative/models/Message.java deleted file mode 100644 index c665d39..0000000 --- a/sdk-base/src/main/java/com/securenative/models/Message.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.securenative.models; - -public class Message { - private Event snEvent; - private String url; - - public Message(Event snEvent, String url) { - this.snEvent = snEvent; - this.url = url; - } - - public Event getSnEvent() { - return snEvent; - } - - public void setSnEvent(Event snEvent) { - this.snEvent = snEvent; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } -} diff --git a/sdk-base/src/main/java/com/securenative/models/RiskLevel.java b/sdk-base/src/main/java/com/securenative/models/RiskLevel.java deleted file mode 100644 index e6199b8..0000000 --- a/sdk-base/src/main/java/com/securenative/models/RiskLevel.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.securenative.models; - -public enum RiskLevel { - high, - low, - medium -} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/securenative/models/SecureNativeOptions.java b/sdk-base/src/main/java/com/securenative/models/SecureNativeOptions.java deleted file mode 100644 index ea6485c..0000000 --- a/sdk-base/src/main/java/com/securenative/models/SecureNativeOptions.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.securenative.models; - -public class SecureNativeOptions { - private String apiUrl; - private int interval; - private long maxEvents; - private long timeout; - private Boolean autoSend; - private Boolean isSdkEnabled; - private Boolean debugMode; - - public SecureNativeOptions(){ - this.autoSend = true; - this.isSdkEnabled = true; - this.debugMode = false; - } - - public SecureNativeOptions(String apiUrl, int interval, long maxEvents, int timeout, boolean autoSend, boolean isSdkEnabled, boolean debugMode) { - this.interval = interval; - this.maxEvents = maxEvents; - this.apiUrl = apiUrl; - this.timeout = timeout; - this.autoSend = autoSend; - this.isSdkEnabled = isSdkEnabled; - this.debugMode = debugMode; - } - - public SecureNativeOptions(String apiUrl, int interval, long maxEvents, int timeout) { - this.interval = interval; - this.maxEvents = maxEvents; - this.apiUrl = apiUrl; - this.timeout = timeout; - this.autoSend = true; - this.isSdkEnabled = true; - this.debugMode = false; - - } - - public String getApiUrl() { - return apiUrl; - } - - public void setApiUrl(String apiUrl) { - this.apiUrl = apiUrl; - } - - public int getInterval() { - return interval; - } - - public void setInterval(int interval) { - this.interval = interval; - } - - public long getMaxEvents() { - return maxEvents; - } - - public void setMaxEvents(long maxEvents) { - this.maxEvents = maxEvents; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - public Boolean isAutoSend() { - return autoSend; - } - - public void setAutoSend(Boolean autoSend) { - this.autoSend = autoSend; - } - - public Boolean getSdkEnabled() { - return isSdkEnabled; - } - - public void setSdkEnabled(Boolean sdkEnabled) { - isSdkEnabled = sdkEnabled; - } - - public Boolean getDebugMode() { - return debugMode; - } - - public void setDebugMode(Boolean debugMode) { - this.debugMode = debugMode; - } -} diff --git a/sdk-base/src/main/java/com/securenative/models/SnEvent.java b/sdk-base/src/main/java/com/securenative/models/SnEvent.java deleted file mode 100644 index 5c53df2..0000000 --- a/sdk-base/src/main/java/com/securenative/models/SnEvent.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.securenative.models; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.securenative.exceptions.SecureNativeSDKException; -import com.securenative.snlogic.Utils; - -import java.nio.charset.Charset; -import java.time.Instant; -import java.util.*; - -public class SnEvent implements Event { - public enum ParamsKeys { - PARAM_1("param_1"), - PARAM_2("param_2"), - PARAM_3("param_3"), - PARAM_4("param_4"), - PARAM_5("param_5"), - PARAM_6("param_6"); - - private String param; - - ParamsKeys(String param) { - this.param = param; - } - @Override - public String toString(){ - return this.param; - } - } - protected static Set paramKeys = new HashSet(Arrays.asList(ParamsKeys.PARAM_1.name(),ParamsKeys.PARAM_2.name(),ParamsKeys.PARAM_3.name(),ParamsKeys.PARAM_4.name(),ParamsKeys.PARAM_5.name(),ParamsKeys.PARAM_6.name())); - - private final static Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); - - private String eventType; - private String cid; - private String vid; - private String fp; - private String ip; - private String remoteIP; - private String userAgent; - private User user; - private long ts; - private Device device; - private String CookieName; - private String CookieValue; - private Map params; - - public static class EventBuilder { - private String eventType; - private String cid; - private String fp; - private String ip; - private String remoteIP; - private String userAgent; - private User user; - private Device device; - private String cookieName; - private String cookieValue; - private Utils utils; - private Map params; - - - public EventBuilder(String eventType) { - this.eventType = eventType; - this.utils = new Utils(); - } - - - public EventBuilder withCid(String cid) { - this.cid = cid; - return this; - } - - public EventBuilder withFp(String fp) { - this.fp = fp; - return this; - } - - public EventBuilder withIp(String ip) { - this.ip = ip; - return this; - } - - public EventBuilder withRemoteIP(String remoteIP) { - this.remoteIP = remoteIP; - return this; - } - - public EventBuilder withUserAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public EventBuilder withUser(User user) { - this.user = user; - return this; - } - - - public EventBuilder withDevice(Device device) { - this.device = device; - return this; - } - - public EventBuilder withCookieValue(String cookieBase64Value) { - if (this.utils.isNullOrEmpty(cookieBase64Value)) { - return this; - } - String decodedCookie = new String(Base64.getDecoder().decode(cookieBase64Value), DEFAULT_CHARSET); - ClientFingerPrint clientFP = this.parseClientFP(decodedCookie); - this.cookieValue = cookieBase64Value; - this.cid = clientFP != null ? clientFP.getCid() : ""; - this.fp = clientFP != null ? clientFP.getFp() : ""; - return this; - } - - public EventBuilder withParams(Map params) throws SecureNativeSDKException{ - if (params == null){ - params = new HashMap<>(); - params.put(ParamsKeys.PARAM_1.toString(),""); - params.put(ParamsKeys.PARAM_2.toString(),""); - params.put(ParamsKeys.PARAM_3.toString(),""); - params.put(ParamsKeys.PARAM_4.toString(),""); - params.put(ParamsKeys.PARAM_5.toString(),""); - params.put(ParamsKeys.PARAM_6.toString(),""); - } - else{ - Iterator i = params.keySet().iterator(); - while(i.hasNext()){ - if(!paramKeys.contains(i)){ - throw new SecureNativeSDKException("Key must be of param_1..param_6"); - } - } - } - this.params = params; - return this; - } - - public Event build() { - SnEvent event = new SnEvent(); - event.eventType = this.eventType; - event.cid = this.cid; - event.vid = UUID.randomUUID().toString(); - event.fp = this.fp; - event.ip = this.ip; - event.remoteIP = this.remoteIP; - event.userAgent = this.userAgent; - event.user = this.user; - event.ts = Instant.now().getEpochSecond(); - event.device = this.device; - event.CookieName = this.cookieName; - event.CookieValue = this.cookieValue; - event.params = this.params; - return event; - } - - private ClientFingerPrint parseClientFP(String json) { - if (this.utils.isNullOrEmpty(json)) { - return null; - } - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.readValue(json, ClientFingerPrint.class); - } catch (Exception e) { - return null; - } - } - - public String base64decode(String encodedString) { - if (this.utils.isNullOrEmpty(encodedString)) { - return ""; - } - return String.valueOf(Base64.getDecoder().decode(encodedString)); - } - } - - - private SnEvent() { - - } - - public String getEventType() { - return eventType; - } - - public void setEventType(String eventType) { - this.eventType = eventType; - } - - public String getCid() { - return cid; - } - - public void setCid(String cid) { - this.cid = cid; - } - - public String getVid() { - return vid; - } - - public void setVid(String vid) { - this.vid = vid; - } - - public String getFp() { - return fp; - } - - public void setFp(String fp) { - this.fp = fp; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getRemoteIP() { - return remoteIP; - } - - public void setRemoteIP(String remoteIP) { - this.remoteIP = remoteIP; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public long getTs() { - return ts; - } - - public void setTs(long ts) { - this.ts = ts; - } - - public Device getDevice() { - return device; - } - - public void setDevice(Device device) { - this.device = device; - } - - - public String getCookieName() { - return CookieName; - } - - public void setCookieName(String cookieName) { - CookieName = cookieName; - } - - public String getCookieValue() { - return CookieValue; - } - - public void setCookieValue(String cookieValue) { - CookieValue = cookieValue; - } - - - public Map getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } -} diff --git a/sdk-base/src/main/java/com/securenative/models/User.java b/sdk-base/src/main/java/com/securenative/models/User.java deleted file mode 100644 index 686bb2b..0000000 --- a/sdk-base/src/main/java/com/securenative/models/User.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.securenative.models; - -public class User { - private String id; - private String name; - private String email; - - - public User(String id, String name, String email) { - this.id = id; - this.name = name; - this.email = email; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } -} diff --git a/sdk-base/src/main/java/com/securenative/snlogic/Agent.java b/sdk-base/src/main/java/com/securenative/snlogic/Agent.java deleted file mode 100644 index a9cc32b..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/Agent.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.securenative.snlogic; - -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.agent.ByteBuddyAgent; -import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; -import net.bytebuddy.implementation.MethodDelegation; -import org.apache.logging.log4j.util.ProcessIdUtil; -import org.reflections.Reflections; -import org.reflections.scanners.ResourcesScanner; - - -import java.lang.reflect.Field; -import java.util.*; -import java.util.regex.Pattern; - -import static net.bytebuddy.matcher.ElementMatchers.*; - -public class Agent { - - - public static String getPomXmlPaths() { - Reflections reflections = new Reflections(new ResourcesScanner()); - Set resources = reflections.getResources(Pattern.compile(".*jar")); - return resources.toString(); - } - - public static void changeClassMethod(Class cls, Class cls1, String methodName) { - ByteBuddyAgent.install(); - new ByteBuddy() - .redefine(cls) - .method(named(methodName) - .and(isDeclaredBy(cls) - .and(returns(String.class)))) - .intercept(MethodDelegation.to(cls1)) - .make() - .load(cls.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()) - .getLoaded(); - } - - public static Set scanAllClasses() { - Field f; - Set locations = new HashSet<>(); - try { - f = ClassLoader.class.getDeclaredField("classes"); - f.setAccessible(true); - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - List classes = Collections.unmodifiableList((Vector) f.get(classLoader)); - for (Class next :classes){ - java.net.URL location = next.getResource('/' + next.getName().replace('.', - '/') + ".class"); - if (location != null && !Utils.isNullOrEmpty(location.getPath())){ - locations.add(location.getPath()); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return locations; - - } - - public static String readVersionInfoInManifest() { - Set locations = new HashSet<>(); - //build an object whose class is in the target jar - ProcessIdUtil object = new ProcessIdUtil(); - //navigate from its class object to a package object - Package objPackage = object.getClass().getPackage(); - //examine the package object - String name = objPackage.getSpecificationTitle(); - String version = objPackage.getSpecificationVersion(); - //some jars may use 'Implementation Version' entries in the manifest instead - return "Package name: " + name + ", Package version: " + version; - } -} diff --git a/sdk-base/src/main/java/com/securenative/snlogic/EventManager.java b/sdk-base/src/main/java/com/securenative/snlogic/EventManager.java deleted file mode 100644 index 1fcf82f..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/EventManager.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.securenative.snlogic; - - -import com.securenative.models.Event; -import com.securenative.models.RiskResult; - -public interface EventManager { - RiskResult sendSync(Event event, String requestUrl); - void sendAsync(Event event, String url); - -} diff --git a/sdk-base/src/main/java/com/securenative/snlogic/ISDK.java b/sdk-base/src/main/java/com/securenative/snlogic/ISDK.java deleted file mode 100644 index dfa1317..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/ISDK.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.securenative.snlogic; - - -import com.securenative.models.Event; -import com.securenative.models.RiskResult; - -public interface ISDK { - void track(Event event) throws Exception; - RiskResult verify(Event event); - RiskResult flow(long flowId, Event event); - String getApiKey(); - String getDefaultCookieName(); -} diff --git a/sdk-base/src/main/java/com/securenative/snlogic/ImpotentLogger.java b/sdk-base/src/main/java/com/securenative/snlogic/ImpotentLogger.java deleted file mode 100644 index 93781c4..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/ImpotentLogger.java +++ /dev/null @@ -1,310 +0,0 @@ -package com.securenative.snlogic; - -import org.slf4j.Marker; - -public class ImpotentLogger implements org.slf4j.Logger { - @Override - public String getName() { - return null; - } - - @Override - public boolean isTraceEnabled() { - return false; - } - - @Override - public void trace(String s) { - - } - - @Override - public void trace(String s, Object o) { - - } - - @Override - public void trace(String s, Object o, Object o1) { - - } - - @Override - public void trace(String s, Object... objects) { - - } - - @Override - public void trace(String s, Throwable throwable) { - - } - - @Override - public boolean isTraceEnabled(Marker marker) { - return false; - } - - @Override - public void trace(Marker marker, String s) { - - } - - @Override - public void trace(Marker marker, String s, Object o) { - - } - - @Override - public void trace(Marker marker, String s, Object o, Object o1) { - - } - - @Override - public void trace(Marker marker, String s, Object... objects) { - - } - - @Override - public void trace(Marker marker, String s, Throwable throwable) { - - } - - @Override - public boolean isDebugEnabled() { - return false; - } - - @Override - public void debug(String s) { - - } - - @Override - public void debug(String s, Object o) { - - } - - @Override - public void debug(String s, Object o, Object o1) { - - } - - @Override - public void debug(String s, Object... objects) { - - } - - @Override - public void debug(String s, Throwable throwable) { - - } - - @Override - public boolean isDebugEnabled(Marker marker) { - return false; - } - - @Override - public void debug(Marker marker, String s) { - - } - - @Override - public void debug(Marker marker, String s, Object o) { - - } - - @Override - public void debug(Marker marker, String s, Object o, Object o1) { - - } - - @Override - public void debug(Marker marker, String s, Object... objects) { - - } - - @Override - public void debug(Marker marker, String s, Throwable throwable) { - - } - - @Override - public boolean isInfoEnabled() { - return false; - } - - @Override - public void info(String s) { - - } - - @Override - public void info(String s, Object o) { - - } - - @Override - public void info(String s, Object o, Object o1) { - - } - - @Override - public void info(String s, Object... objects) { - - } - - @Override - public void info(String s, Throwable throwable) { - - } - - @Override - public boolean isInfoEnabled(Marker marker) { - return false; - } - - @Override - public void info(Marker marker, String s) { - - } - - @Override - public void info(Marker marker, String s, Object o) { - - } - - @Override - public void info(Marker marker, String s, Object o, Object o1) { - - } - - @Override - public void info(Marker marker, String s, Object... objects) { - - } - - @Override - public void info(Marker marker, String s, Throwable throwable) { - - } - - @Override - public boolean isWarnEnabled() { - return false; - } - - @Override - public void warn(String s) { - - } - - @Override - public void warn(String s, Object o) { - - } - - @Override - public void warn(String s, Object... objects) { - - } - - @Override - public void warn(String s, Object o, Object o1) { - - } - - @Override - public void warn(String s, Throwable throwable) { - - } - - @Override - public boolean isWarnEnabled(Marker marker) { - return false; - } - - @Override - public void warn(Marker marker, String s) { - - } - - @Override - public void warn(Marker marker, String s, Object o) { - - } - - @Override - public void warn(Marker marker, String s, Object o, Object o1) { - - } - - @Override - public void warn(Marker marker, String s, Object... objects) { - - } - - @Override - public void warn(Marker marker, String s, Throwable throwable) { - - } - - @Override - public boolean isErrorEnabled() { - return false; - } - - @Override - public void error(String s) { - - } - - @Override - public void error(String s, Object o) { - - } - - @Override - public void error(String s, Object o, Object o1) { - - } - - @Override - public void error(String s, Object... objects) { - - } - - @Override - public void error(String s, Throwable throwable) { - - } - - @Override - public boolean isErrorEnabled(Marker marker) { - return false; - } - - @Override - public void error(Marker marker, String s) { - - } - - @Override - public void error(Marker marker, String s, Object o) { - - } - - @Override - public void error(Marker marker, String s, Object o, Object o1) { - - } - - @Override - public void error(Marker marker, String s, Object... objects) { - - } - - @Override - public void error(Marker marker, String s, Throwable throwable) { - - } -} diff --git a/sdk-base/src/main/java/com/securenative/snlogic/Logger.java b/sdk-base/src/main/java/com/securenative/snlogic/Logger.java deleted file mode 100644 index c08f0b1..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/Logger.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.securenative.snlogic; - -import org.slf4j.LoggerFactory; - -public class Logger{ - private static org.slf4j.Logger logger = new ImpotentLogger(); - - public static org.slf4j.Logger getLogger() { - return logger; - } - - public static void setLoggingEnable(boolean isLoggingEnabledInput) { - if(isLoggingEnabledInput){ - logger = LoggerFactory.getLogger(Logger.class); - logger.info("Secure Native logger is enabled"); - return; - } - logger = new ImpotentLogger(); - - } -} - diff --git a/sdk-base/src/main/java/com/securenative/snlogic/SecureNative.java b/sdk-base/src/main/java/com/securenative/snlogic/SecureNative.java deleted file mode 100644 index 04e48dd..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/SecureNative.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.securenative.snlogic; - - -import com.securenative.exceptions.SecureNativeSDKException; -import com.securenative.models.Event; -import com.securenative.models.RiskResult; -import com.securenative.models.SecureNativeOptions; - -public class SecureNative implements ISDK { - private final String API_URL = "https://api.securenative.com/collector/api/v1"; - private final int INTERVAL = 1000; - private final int MAX_EVENTS = 1000; - private final Boolean AUTO_SEND = true; - private final Boolean SDK_ENABLED = true; - private final Boolean DEBUG_LOG = false; - private final int DEFAULT_TIMEOUT = 1500; - - private EventManager eventManager; - private SecureNativeOptions snOptions; - private String apiKey; - private Utils utils; - - private static ISDK secureNative = null; - - - private SecureNative(String apiKey, SecureNativeOptions options) throws SecureNativeSDKException { - this.utils = new Utils(); - if (this.utils.isNullOrEmpty(apiKey)) { - throw new SecureNativeSDKException("You must pass your SecureNative api key"); - } - this.apiKey = apiKey; - this.snOptions = initializeOptions(options); - this.eventManager = new SnEventManager(apiKey,this.snOptions); - Logger.setLoggingEnable(this.snOptions.getDebugMode()); - } - - - public static ISDK init(String apiKey, SecureNativeOptions options) throws SecureNativeSDKException { - if (secureNative == null) { - secureNative = new SecureNative(apiKey, options); - if(options != null && options.getDebugMode() != null){ - Logger.setLoggingEnable(options.getDebugMode()); - } - return secureNative; - } - throw new SecureNativeSDKException("This SDK was already initialized"); - } - - public static ISDK getInstance() throws SecureNativeSDKException { - if (secureNative == null) { - throw new SecureNativeSDKException("Secure Native SDK wasnt initialized yet, please call init first"); - } - return secureNative; - } - - private SecureNativeOptions initializeOptions(SecureNativeOptions options) { - if (options == null) { - Logger.getLogger().info("SecureNative options are empty, initializing default values"); - options = new SecureNativeOptions(); - } - if (this.utils.isNullOrEmpty(options.getApiUrl())) { - options.setApiUrl(API_URL); - } - - if (options.getInterval() == 0) { - options.setInterval(INTERVAL); - } - - if (options.getMaxEvents() == 0) { - options.setMaxEvents(MAX_EVENTS); - } - if (options.isAutoSend() == null) { - options.setAutoSend(AUTO_SEND); - } - if (options.getSdkEnabled() == null){ - options.setSdkEnabled(SDK_ENABLED); - } - if (options.getDebugMode() == null){ - options.setSdkEnabled(DEBUG_LOG); - } - if(options.getTimeout() == 0){ - options.setTimeout(DEFAULT_TIMEOUT); - } - if(options.getDebugMode() == null){ - options.setDebugMode(false); - } - - return options; - } - - @Override - public String getDefaultCookieName(){ - return this.utils.COOKIE_NAME; - } - - @Override - public void track(Event event) { - Logger.getLogger().info("Track event call"); - this.eventManager.sendAsync(event, this.snOptions.getApiUrl() + "/track"); - } - - @Override - public RiskResult verify(Event event) { - Logger.getLogger().info("Verify event call"); - return this.eventManager.sendSync(event, this.snOptions.getApiUrl() + "/verify"); - } - - @Override - public RiskResult flow(long flowId, Event event) {//FOR FUTURE PURPOSES - Logger.getLogger().info("Flow event call"); - return this.eventManager.sendSync(event, this.snOptions.getApiUrl() + "/flow/" + flowId); - } - - @Override - public String getApiKey() { - return apiKey; - } - - } \ No newline at end of file diff --git a/sdk-base/src/main/java/com/securenative/snlogic/SnEventManager.java b/sdk-base/src/main/java/com/securenative/snlogic/SnEventManager.java deleted file mode 100644 index 92ffcbc..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/SnEventManager.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.securenative.snlogic; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.securenative.exceptions.SecureNativeSDKException; -import com.securenative.models.*; -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.asynchttpclient.*; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - - -public class SnEventManager implements EventManager { - private final String USER_AGENT_VALUE = "com.securenative.snlogic.SecureNative-java"; - private final String SN_VERSION = "SN-Version"; - private BoundRequestBuilder asyncClient; - private String apiKey; - private Utils utils; - private ExecutorService executor; - private ConcurrentLinkedQueue events; - private ObjectMapper mapper; - private int HTTP_STATUS_OK = 201; - private String AUTHORIZATION = "Authorization"; - private SecureNativeOptions options; - RiskResult defaultRiskResult = new RiskResult(RiskLevel.low.name(), 0.0, new String[0]); - - public SnEventManager(String apiKey, SecureNativeOptions options) throws SecureNativeSDKException { - this.utils = new Utils(); - this.options = options; - events = new ConcurrentLinkedQueue<>(); - if (this.utils.isNullOrEmpty(apiKey) || options == null) { - throw new SecureNativeSDKException("You must pass a valid api key"); - } - this.asyncClient = initializeAsyncHttpClient(options); - this.apiKey = apiKey; - - if (this.options.getSdkEnabled() != null && !this.options.getSdkEnabled()) { - executor = Executors.newSingleThreadScheduledExecutor(); - Logger.getLogger().info(String.format("Starting thread listening to messages queue")); - executor.execute(() -> { - try { - Thread.sleep((long) (Math.random() * 1000)); - Message msg = events.poll(); - if (msg != null) { - sendSync(msg.getSnEvent(), msg.getUrl()); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - } - mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } - - - @Override - public RiskResult sendSync(Event event, String url) { - if (this.options.getSdkEnabled() != null && !this.options.getSdkEnabled()) { - return defaultRiskResult; - } - - this.asyncClient.setHeader(AUTHORIZATION, this.apiKey).setUrl(url); - - try { - this.asyncClient.setBody(mapper.writeValueAsString(event)); - Response response = this.asyncClient.execute().get(); - if (response == null || response.getStatusCode() > HTTP_STATUS_OK) { - Logger.getLogger().info(String.format("Secure Native http call failed to end point: %s with event type %s. adding back to queue.", url, event.getEventType())); - events.add(new Message(event, response.getUri().toUrl())); - } - String responseBody = response.getResponseBody(); - if (utils.isNullOrEmpty(responseBody)) { - Logger.getLogger().info(String.format("Secure Native http call to %s returned with empty response. returning default risk result.", url)); - return defaultRiskResult; - } - Logger.getLogger().info(String.format("Secure Native http call to %s was successful.", url)); - return mapper.readValue(responseBody, RiskResult.class); - } catch (InterruptedException | ExecutionException | IOException e) { - e.printStackTrace(); - } - return defaultRiskResult; - - } - - @Override - public void sendAsync(Event event, String url) { - if (this.options.getSdkEnabled() != null && !this.options.getSdkEnabled()) { - return; - } - this.asyncClient.setUrl(url).setHeader(AUTHORIZATION, this.apiKey); - try { - this.asyncClient.setBody(mapper.writeValueAsString(event)); - } catch (JsonProcessingException e) { - Logger.getLogger().info(String.format("Secure Native async http call failed to end point: %s with event type %s. error: %s", url, event.getEventType(), e)); - } - - this.asyncClient.execute( - new AsyncCompletionHandler() { - @Override - public Object onCompleted(Response response) { - if (response.getStatusCode() > HTTP_STATUS_OK) { - Logger.getLogger().info(String.format("Secure Native http call failed to end point: %s with event type %s. adding back to queue.", url, event.getEventType())); - events.add(new Message(event, response.getUri().toUrl())); - } - return response; - } - }); - - } - - private BoundRequestBuilder initializeAsyncHttpClient(SecureNativeOptions options) { - DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config() - .setConnectTimeout((int) options.getTimeout()) - .setUserAgent(USER_AGENT_VALUE); - AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder); - Logger.getLogger().info("Initialized Http client"); - return client.preparePost(options.getApiUrl()) - .addHeader(SN_VERSION, this.getVersion()).addHeader("Accept", "application/json"); - - } - - private String getVersion() { - try { - MavenXpp3Reader reader = new MavenXpp3Reader(); - String pomResource = "/META-INF/maven/com.securenative.java/sdk-base/pom.xml"; - Model read = reader.read(new InputStreamReader(SnEventManager.class.getResourceAsStream(pomResource))); - return read.getParent().getVersion(); - } catch (Exception e) { - return "unknown"; - } - } - -} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/securenative/snlogic/Utils.java b/sdk-base/src/main/java/com/securenative/snlogic/Utils.java deleted file mode 100644 index 11e2715..0000000 --- a/sdk-base/src/main/java/com/securenative/snlogic/Utils.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.securenative.snlogic; - -import com.securenative.exceptions.SecureNativeSDKException; - -import javax.crypto.*; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.AlgorithmParameterSpec; -import java.util.Arrays; -import java.util.Formatter; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class Utils { - private static String[] ipHeaders = {"x-forwarded-for", "x-client-ip", "x-real-ip", "x-forwarded", "x-cluster-client-ip", "forwarded-for", "forwarded", "via"}; - public String COOKIE_NAME = "_sn"; - public final String SN_HEADER = "x-securenative"; - public final String USERAGENT_HEADER = "user-agent"; - private final String HMAC_SHA1_ALGORITHM = "HmacSHA512"; - private Pattern VALID_IPV6_PATTERN = Pattern.compile("([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}", Pattern.CASE_INSENSITIVE); - private final int AES_KEY_SIZE = 32; - - - public Utils() { - } - - - public String remoteIpFromRequest(Function headerExtractor) { - Logger.getLogger().info("Extracting remote ip from requests"); - Optional bestCandidate = Optional.empty(); - String header = ""; - for (int i = 0; i < ipHeaders.length; i++) { - List candidates = Arrays.asList(); - header = headerExtractor.apply(ipHeaders[i]); - if (!this.isNullOrEmpty(header)) { - candidates = Arrays.stream(header.split(",")).map(s -> s.trim()).filter(s -> !this.isNullOrEmpty(s) && - (isValidInet4Address(s) || this.isIpV6Address(s)) && - !isPrivateIPAddress(s)).collect(Collectors.toList()); - if (candidates.size() > 0) { - Logger.getLogger().info(String.format("Extracted remote ip %s",candidates.get(0))); - return candidates.get(0); - } - } - if (!bestCandidate.isPresent()) { - bestCandidate = candidates.stream().filter(x -> isLoopBack(x)).findFirst(); - } - } - Logger.getLogger().info("couldn't extract remote ip, returning 127.0.0.1"); - return "127.0.0.1"; - } - - - private boolean isLoopBack(String ip) { - try { - return InetAddress.getByName(ip).isLoopbackAddress(); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - return false; - } - - private boolean isPrivateIPAddress(String ipAddress) { - InetAddress ia = null; - try { - InetAddress ad = InetAddress.getByName(ipAddress); - byte[] ip = ad.getAddress(); - ia = InetAddress.getByAddress(ip); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - return ia.isSiteLocalAddress(); - } - - private String toHexString(byte[] bytes) { - Formatter formatter = new Formatter(); - - for (byte b : bytes) { - formatter.format("%02x", b); - } - - return formatter.toString(); - } - - private String calculateRFC2104HMAC(String data, String key) throws SecureNativeSDKException { - try { - SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); - Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - return toHexString(mac.doFinal(data.getBytes())); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new SecureNativeSDKException("failed calculating hmac"); - } - } - - private String calculateSignature(String payload, String apikey) { - if (this.isNullOrEmpty(payload)) { - return null; - } - try { - return this.calculateRFC2104HMAC(payload, apikey); - } catch (SecureNativeSDKException e) { - return null; - } - } - - public boolean isVerifiedSnRequest(String payload, String hedaerSignature, String apiKey) { - String signed = calculateSignature(payload, apiKey); - if (this.isNullOrEmpty(signed) || this.isNullOrEmpty(hedaerSignature)) { - return false; - } - return hedaerSignature.equals(signed); - } - - public static boolean isNullOrEmpty(final String s) { - return s == null || s.length() == 0; - } - - private boolean isValidInet4Address(String ip) { - String[] groups = ip.split("\\."); - if (groups.length != 4) - return false; - try { - return Arrays.stream(groups) - .filter(s -> s.length() > 1 && s.startsWith("0")) - .map(Integer::parseInt) - .filter(i -> (i >= 0 && i <= 255)) - .count() == 4; - - } catch (NumberFormatException e) { - return false; - } - } - - private boolean isIpV6Address(String ipAddress) { - return this.VALID_IPV6_PATTERN.matcher(ipAddress).matches(); - } - - public String decrypt(String s, String key) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, - IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { - Logger.getLogger().info("Starting to decrypt " + s); - if (s == null || s.length() == 0) { - return s; - } - byte[] cipherText = hexToByteArray(s); - SecretKeySpec skeySpec = new SecretKeySpec(key.substring(0, 32).getBytes("UTF-8"), "AES"); - byte[] ivBytes = Arrays.copyOfRange(cipherText, 0, AES_KEY_SIZE / 2); - cipherText = Arrays.copyOfRange(cipherText, AES_KEY_SIZE / 2, cipherText.length); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - AlgorithmParameterSpec IVspec = new IvParameterSpec(ivBytes); - cipher.init(Cipher.DECRYPT_MODE, skeySpec, IVspec); - return new String(cipher.doFinal(cipherText), "UTF-8").trim(); - } - - private byte[] hexToByteArray(String s) { - byte[] retValue = null; - if (s != null && s.length() != 0) { - retValue = new byte[s.length() / 2]; - for (int i = 0; i < retValue.length; i++) { - retValue[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16); - } - } - return retValue; - } - private final static char[] HEX = new char[]{ - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - - private static String byteArrayToHex(byte[] byteArray) { - StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2); - for (byte b : byteArray) - for (int j = 1; j >= 0; j--) - hexBuffer.append(HEX[(b >> (j * 4)) & 0xF]); - return hexBuffer.toString(); - } - - private byte[] pad (byte[] buf, int size){ - int bufLen = buf.length; - int padLen = size - bufLen%size; - byte[] padded = new byte[bufLen+padLen]; - padded = Arrays.copyOf(buf,bufLen+padLen); - for (int i = 0; i < padLen; i++) { - padded[bufLen+i] = (byte)padLen; - } - return padded; - } - - public static String encrypt(String text, String key) - throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, - IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { - SecureRandom secureRandom = new SecureRandom(); - byte[] ivBytes = new byte[16]; - secureRandom.nextBytes(ivBytes); - AlgorithmParameterSpec IVspec = new IvParameterSpec(ivBytes); - SecretKeySpec skeySpec = new SecretKeySpec(key.substring(0, 32).getBytes(StandardCharsets.UTF_8), "AES"); - byte[] source = text.getBytes(StandardCharsets.UTF_8); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, skeySpec, IVspec); - int mod = source.length % 16; - if (mod != 0) { - text = String.format(text + "%" + (16 - mod) + "s", " "); - } - return byteArrayToHex(cipher.doFinal(addAll(ivBytes,text.getBytes("UTF-8")))).trim(); - } - private static byte[] addAll(final byte[] array1, byte[] array2) { - byte[] joinedArray = Arrays.copyOf(array1, array1.length + array2.length); - System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); - return joinedArray; - } -} diff --git a/sdk-base/test/main/java/com/securenative/snlogic/AgentTest.java b/sdk-base/test/main/java/com/securenative/snlogic/AgentTest.java deleted file mode 100644 index 3cb4286..0000000 --- a/sdk-base/test/main/java/com/securenative/snlogic/AgentTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.securenative.snlogic; - -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.IOException; - -import static com.securenative.snlogic.Agent.*; - -public class AgentTest { - - @BeforeClass - public static void setup(){ - - } - - @Test - public void testPomPath(){ - String pomXmlPaths = getPomXmlPaths(); - Assert.assertTrue(pomXmlPaths.equals("debugger-agent-storage.jar")); - System.out.println(pomXmlPaths); - } - - @Test - public void changeClassTest(){ - changeClassMethod(TestClassForByteBuddy.class, TestClassForByteBuddy2.class, "returnOne"); - TestClassForByteBuddy testClassForByteBuddy = new TestClassForByteBuddy(); - Assert.assertTrue(testClassForByteBuddy.returnOne().equals("2")); - } - - @Test - public void test() throws IOException { - Assert.assertTrue(readVersionInfoInManifest().equals("Package name: Apache Log4j API, Package version: 2.11.2")); - } - -} diff --git a/sdk-base/test/main/java/com/securenative/snlogic/SecureNativeTest.java b/sdk-base/test/main/java/com/securenative/snlogic/SecureNativeTest.java deleted file mode 100644 index a566636..0000000 --- a/sdk-base/test/main/java/com/securenative/snlogic/SecureNativeTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.securenative.snlogic; - - -import com.securenative.exceptions.SecureNativeSDKException; -import com.securenative.models.Device; -import com.securenative.models.Event; -import com.securenative.models.EventTypes; -import com.securenative.models.SnEvent; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.Mockito.mock; - -public class SecureNativeTest{ - static Utils utils; - static ISDK sn; - static String API_KEY = "ApiKey"; - - @BeforeClass - public static void runOnceBeforeClass() throws SecureNativeSDKException { - utils = mock(Utils.class); - sn = SecureNative.init(API_KEY, null); - } - - @Test - public void makeSureItsTheSameInstanceTest() throws Exception { - ISDK sn0 = SecureNative.getInstance(); - Assert.assertEquals(sn0.getApiKey(),API_KEY); - } - - @Test - public void TestBasicTrack() throws Exception { - ISDK sn1 = SecureNative.getInstance(); - Event event = new SnEvent.EventBuilder(EventTypes.LOG_OUT.getType()).withCookieValue("ewoJImNpZCI6ICJjaWRWYWx1ZSIsCgkiZnAiOiAidiIKfQ==").withDevice(new Device("id")).withIp("ip").build(); - sn1.track(event); - } - - @Test(expected = SecureNativeSDKException.class) - public void callTrackWith7CustomParams() throws SecureNativeSDKException { - Map params = new HashMap(); - params.put("param_1", "one"); - params.put("param_2", "two"); - params.put("param_3", "three"); - params.put("param_4", "four"); - params.put("param_5", "five"); - params.put("param_6", "six"); - params.put("param_7", "seven"); - new SnEvent.EventBuilder("event").withParams(params).build(); - } - - @Test - public void callTrackWithNullCustomParams() throws SecureNativeSDKException { - Event event = new SnEvent.EventBuilder("event").withParams(null).build(); - Assert.assertEquals(event.getParams().size(),6); - Assert.assertTrue(event.getParams().keySet().contains("param_1")); - Assert.assertTrue(event.getParams().keySet().contains("param_6")); - } - - @Test - public void testDecryption() throws Exception { - String cookie = "821cb59a6647f1edf597956243e564b00c120f8ac1674a153fbd707da0707fb236ea040d1665f3d294aa1943afbae1b26b2b795a127f883ec221c10c881a147bb8acb7e760cd6f04edc21c396ee1f6c9627d9bf1315c484a970ce8930c2ed1011af7e8569325c7edcdf70396f1abca8486eabec24567bf215d2e60382c40e5c42af075379dacdf959cb3fef74f9c9d15"; - String apikey = "6EA4915349C0AAC6F6572DA4F6B00C42DAD33E75"; - Utils utils = new Utils(); - String a = utils.decrypt(cookie,apikey); - String e = "{\"cid\":\"198a41ff-a10f-4cda-a2f3-a9ca80c0703b\",\"fp\":\"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed\"}"; - Assert.assertEquals(e,a); - } - - @Test - public void testEncryptionDecryption() throws Exception { - String apikey = "6EA4915349C0AAC6F6572DA4F6B00C42DAD33E75"; - String e = "{\"cid\":\"198a41ff-a10f-4cda-a2f3-a9ca80c0703b\",\"fp\":\"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed\"}"; - Utils utils = new Utils(); - String a = utils.decrypt(utils.encrypt(e,apikey),apikey); - Assert.assertEquals(e,a); - } -} \ No newline at end of file diff --git a/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy.java b/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy.java deleted file mode 100644 index c3b45a9..0000000 --- a/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.securenative.snlogic; - -public class TestClassForByteBuddy { - public TestClassForByteBuddy() { - } - public String returnOne(){ - return "1"; - } -} - - diff --git a/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy2.java b/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy2.java deleted file mode 100644 index dc5d051..0000000 --- a/sdk-base/test/main/java/com/securenative/snlogic/TestClassForByteBuddy2.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.securenative.snlogic; - -public class TestClassForByteBuddy2 { - public TestClassForByteBuddy2() { - } - public static String returnOne(){ - return "2"; - } -} diff --git a/spring/pom.xml b/spring/pom.xml deleted file mode 100644 index 2f83b87..0000000 --- a/spring/pom.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - sdk-parent - com.securenative.java - 0.3.0 - - 4.0.0 - spring - - ${project.artifactId} - Secure Native SDK in java - https://github.com/securenative/securenative-java - - - The MIT License - https://opensource.org/licenses/MIT - - - - - Nevo Elmalem - nevo@securenative.com - securenative - http://www.securenative.com - - - - scm:git:git://github.com/securenative/securenative-java.git - scm:git:ssh://github.com:securenative/securenative-java.git - http://github.com/securenative/securenative-java/tree/master - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - - - maven-deploy-plugin - 2.8.2 - - - default-deploy - deploy - - deploy - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - maven-assembly-plugin - - - - your.MainClass - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - - com.securenative.java - sdk-base - 0.2.4 - - - javax.servlet - servlet-api - 2.5 - provided - - - junit - junit - 4.12 - test - - - org.mockito - mockito-core - 2.21.0 - test - - - \ No newline at end of file diff --git a/spring/src/main/java/com/securenative/spring/VerifyRequestMiddleware.java b/spring/src/main/java/com/securenative/spring/VerifyRequestMiddleware.java deleted file mode 100644 index 5edc625..0000000 --- a/spring/src/main/java/com/securenative/spring/VerifyRequestMiddleware.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.securenative.spring; - -import com.securenative.snlogic.ISDK; -import com.securenative.snlogic.SecureNative; -import com.securenative.snlogic.Utils; - -import javax.servlet.*; -import java.io.IOException; - -public class VerifyRequestMiddleware implements Filter { - - private ISDK sn; - - private Utils utils; - - public VerifyRequestMiddleware(SecureNative sn) { - this.sn = sn; - } - - - @Override - public void init(FilterConfig filterConfig){ - utils = new Utils(); - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - - } - - @Override - public void destroy() {} -} diff --git a/spring/src/main/java/com/securenative/spring/VerifyWebHookMiddleware.java b/spring/src/main/java/com/securenative/spring/VerifyWebHookMiddleware.java deleted file mode 100644 index 58de7d3..0000000 --- a/spring/src/main/java/com/securenative/spring/VerifyWebHookMiddleware.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.securenative.spring; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.securenative.models.*; -import com.securenative.snlogic.Logger; -import com.securenative.snlogic.Utils; - -import javax.servlet.*; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Arrays; -import java.util.Optional; - -public class VerifyWebHookMiddleware implements Filter { - private String apikey; - private Utils utils; - private final String EMPTY = ""; - private final String SINATURE_KEY = "x-securenative"; - private ObjectMapper mapper; - - - public VerifyWebHookMiddleware(String apiKey) { - this.apikey = apiKey; - this.utils = new Utils(); - mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - - } - - @Override - public void init(FilterConfig filterConfig) { - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) servletRequest; - HttpServletResponse res = (HttpServletResponse) servletResponse; - - String signature = ""; - if (req != null && !this.utils.isNullOrEmpty(req.getHeader(SINATURE_KEY))) { - signature = req.getHeader(SINATURE_KEY); - } - String payload = getBody(servletRequest); - if (utils.isVerifiedSnRequest(payload, signature, this.apikey)) { - filterChain.doFilter(req, res); - return; - } - Logger.getLogger().info("Request have been blocked due to incompatible signature"); - res.sendError(401, "Unauthorized"); - return; - } - - - @Override - public void destroy() { - - } - - private String getBody(ServletRequest servletRequest) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - BufferedReader bufferedReader = null; - try { - InputStream inputStream = servletRequest.getInputStream(); - if (inputStream != null) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - char[] charBuffer = new char[128]; - int bytesRead = -1; - while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { - stringBuilder.append(charBuffer, 0, bytesRead); - } - } - } catch (IOException ex) { - throw ex; - } finally { - if (bufferedReader != null) { - try { - bufferedReader.close(); - } catch (IOException ex) { - throw ex; - } - } - } - return stringBuilder.toString(); - } - - public String getCookie(HttpServletRequest request, final String cookieName) { - if (request == null || request.getCookies() == null || request.getCookies().length == 0) { - return null; - } - Optional cookie = Arrays.stream(request.getCookies()).filter(x -> (utils.isNullOrEmpty(cookieName) ? utils.COOKIE_NAME : cookieName).equals(x.getName())).findFirst(); - return cookie.isPresent() ? cookie.get().getValue() : null; - } - - public String remoteIpFromServletRequest(HttpServletRequest request) { - if (request == null) { - return EMPTY; - } - return utils.remoteIpFromRequest(request::getHeader); - } - - public Event buildEventFromHttpServletRequest(HttpServletRequest request, Event event) { - Logger.getLogger().info(String.format("building event from http servlet request")); - String encodedCookie = getCookie(request, event != null && !this.utils.isNullOrEmpty(event.getCookieName()) ? event.getCookieName() : this.utils.COOKIE_NAME); - encodedCookie = utils.isNullOrEmpty(encodedCookie) && !utils.isNullOrEmpty(event.getCookieValue()) ? event.getCookieValue() : encodedCookie; - Logger.getLogger().info("Decoding cookie " + encodedCookie); - String decodedCookie = ""; - ClientFingerPrint clientFingerPrint = new ClientFingerPrint("", ""); - try { - decodedCookie = utils.decrypt(encodedCookie, this.apikey); - clientFingerPrint = mapper.readValue(decodedCookie, ClientFingerPrint.class); - } catch (Exception e) { - Logger.getLogger().info(String.format("Failed decoding cookie %s", encodedCookie)); - } - String eventype = event == null || this.utils.isNullOrEmpty(event.getEventType()) ? EventTypes.LOG_IN.getType() : event.getEventType(); - String ip = event != null && event.getIp() != null ? event.getIp() : remoteIpFromServletRequest(request); - String remoteIP = request.getRemoteAddr(); - String userAgent = event != null && event.getUserAgent() != null ? event.getUserAgent() : request.getHeader(this.utils.USERAGENT_HEADER); - User user = event != null && event.getUser() != null ? event.getUser() : new User(null, null, "anonymous"); - Device device = event != null && event.getDevice() != null ? event.getDevice() : null; - return new SnEvent.EventBuilder(eventype).withCookieValue(this.utils.isNullOrEmpty(decodedCookie) ? request.getHeader(utils.SN_HEADER) : encodedCookie).withIp(ip).withRemoteIP(remoteIP).withUserAgent(userAgent).withUser(user).withDevice(device).withCid(clientFingerPrint.getCid()).withFp(clientFingerPrint.getFp()). - build(); - } - - -} diff --git a/spring/src/test/java/com/securenative/snlogic/VerifyWebHookMiddlewareTest.java b/spring/src/test/java/com/securenative/snlogic/VerifyWebHookMiddlewareTest.java deleted file mode 100644 index 3ad9510..0000000 --- a/spring/src/test/java/com/securenative/snlogic/VerifyWebHookMiddlewareTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.securenative.snlogic; - - -import com.securenative.models.Device; -import com.securenative.models.Event; -import com.securenative.models.EventTypes; -import com.securenative.models.SnEvent; -import com.securenative.spring.VerifyWebHookMiddleware; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class VerifyWebHookMiddlewareTest { - - HttpServletRequest request; - Utils utils; - VerifyWebHookMiddleware v; - String key = "6EA4915349C0AAC6F6572DA4F6B00C42DAD33E75"; - String encrypted = "821cb59a6647f1edf597956243e564b00c120f8ac1674a153fbd707da0707fb236ea040d1665f3d294aa1943afbae1b26b2b795a127f883ec221c10c881a147bb8acb7e760cd6f04edc21c396ee1f6c9627d9bf1315c484a970ce8930c2ed1011af7e8569325c7edcdf70396f1abca8486eabec24567bf215d2e60382c40e5c42af075379dacdf959cb3fef74f9c9d15"; - String a = "{\"cid\":\"198a41ff-a10f-4cda-a2f3-a9ca80c0703b\",\"fp\":\"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed\"}"; - - @Before - public void setup() { - v = new VerifyWebHookMiddleware(key); - request = mock(HttpServletRequest.class); - utils = mock(Utils.class); - - } - - @Test - public void buildEventFromHttpServletWhenEventNullTest() throws Exception { - when(request.getCookies()).thenReturn(new Cookie[]{new Cookie("_sn",encrypted),new Cookie("n","v")}); - when(request.getHeader("header")).thenReturn("header"); - when(request.getHeader(this.utils.USERAGENT_HEADER)).thenReturn("user_agent_header_test"); - when(request.getRemoteAddr()).thenReturn("address"); - Event event = v.buildEventFromHttpServletRequest(request, null); - Assert.assertEquals(event.getEventType(),"sn.user.login"); - Assert.assertEquals(event.getCid(),"198a41ff-a10f-4cda-a2f3-a9ca80c0703b"); - Assert.assertEquals(event.getFp(),"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed"); - Assert.assertEquals(event.getIp(),"127.0.0.1"); - Assert.assertEquals(event.getRemoteIP(),"address"); - Assert.assertEquals(event.getUserAgent(),"user_agent_header_test"); - Assert.assertEquals(event.getUser().getEmail(),"anonymous"); - Assert.assertEquals(event.getCookieValue(),encrypted); - } - - @Test - public void buildEventFromhttpServletWhenEventValidTest() throws Exception { - when(request.getHeader("header")).thenReturn("header"); - when(request.getHeader(this.utils.USERAGENT_HEADER)).thenReturn("user_agent_header_test"); - when(request.getRemoteAddr()).thenReturn("address"); - Event event = v.buildEventFromHttpServletRequest(request, new SnEvent.EventBuilder(EventTypes.LOG_OUT.getType()).withCookieValue(encrypted).withDevice(new Device("id")).withIp("ip").build()); - Assert.assertEquals(event.getEventType(),"sn.user.logout"); - Assert.assertEquals(event.getCid(),"198a41ff-a10f-4cda-a2f3-a9ca80c0703b"); - Assert.assertEquals(event.getFp(),"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed"); - Assert.assertEquals(event.getIp(),"ip"); - Assert.assertEquals(event.getDevice().getId(),"id"); - Assert.assertEquals(event.getCookieValue(),encrypted); - } - - @Test - public void testEncryptionDecryption() throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException { - Utils utilsEnc = new Utils(); - String enc = utilsEnc.encrypt(a,key); - String dec = utilsEnc.decrypt(enc,key); - Assert.assertEquals(a,dec); - - } -} diff --git a/src/.DS_Store b/src/.DS_Store index 7b0d367..e1029c6 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/main/java/.DS_Store b/src/main/java/.DS_Store index c43ee62..5dfa962 100644 Binary files a/src/main/java/.DS_Store and b/src/main/java/.DS_Store differ diff --git a/src/main/java/com/securenative/ApiManager.java b/src/main/java/com/securenative/ApiManager.java new file mode 100644 index 0000000..551f4f1 --- /dev/null +++ b/src/main/java/com/securenative/ApiManager.java @@ -0,0 +1,11 @@ +package com.securenative; + +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.models.EventOptions; +import com.securenative.models.VerifyResult; + +public interface ApiManager { + void track(EventOptions eventOptions) throws SecureNativeInvalidOptionsException; + + VerifyResult verify(EventOptions eventOptions) throws SecureNativeInvalidOptionsException; +} diff --git a/src/main/java/com/securenative/ApiManagerImpl.java b/src/main/java/com/securenative/ApiManagerImpl.java new file mode 100644 index 0000000..f7d7231 --- /dev/null +++ b/src/main/java/com/securenative/ApiManagerImpl.java @@ -0,0 +1,44 @@ +package com.securenative; + +import com.securenative.config.SecureNativeOptions; +import com.securenative.enums.ApiRoute; +import com.securenative.enums.FailoverStrategy; +import com.securenative.enums.RiskLevel; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.models.Event; +import com.securenative.models.EventOptions; +import com.securenative.models.SDKEvent; +import com.securenative.models.VerifyResult; + +public class ApiManagerImpl implements ApiManager { + private final EventManager eventManager; + private final SecureNativeOptions options; + public static final Logger logger = Logger.getLogger(SecureNative.class); + + public ApiManagerImpl(EventManager eventManager, SecureNativeOptions options) throws SecureNativeSDKException { + this.eventManager = eventManager; + this.options = options; + } + + @Override + public void track(EventOptions eventOptions) throws SecureNativeInvalidOptionsException { + logger.info("Track event call"); + Event event = new SDKEvent(eventOptions, this.options); + this.eventManager.sendAsync(event, ApiRoute.TRACK.getApiRoute(), true); + } + + @Override + public VerifyResult verify(EventOptions eventOptions) throws SecureNativeInvalidOptionsException { + logger.info("Verify event call"); + Event event = new SDKEvent(eventOptions, this.options); + try { + return this.eventManager.sendSync(VerifyResult.class, event, ApiRoute.VERIFY.getApiRoute()); + } catch (Exception ex) { + logger.error("Failed to call verify", ex); + return this.options.getFailoverStrategy() == FailoverStrategy.FAIL_OPEN ? + new VerifyResult(RiskLevel.LOW, 0, new String[0]) + : new VerifyResult(RiskLevel.HIGH, 1, new String[0]); + } + } +} diff --git a/src/main/java/com/securenative/EventManager.java b/src/main/java/com/securenative/EventManager.java new file mode 100644 index 0000000..a81d301 --- /dev/null +++ b/src/main/java/com/securenative/EventManager.java @@ -0,0 +1,16 @@ +package com.securenative; + +import com.securenative.exceptions.SecureNativeParseException; +import com.securenative.models.Event; + +import java.io.IOException; + +public interface EventManager { + T sendSync(Class clazz, Event event, String url) throws IOException, SecureNativeParseException; + + void sendAsync(Event event, String url, Boolean retry); + + void startEventsPersist(); + + void stopEventsPersist(); +} diff --git a/src/main/java/com/securenative/EventOptionsBuilder.java b/src/main/java/com/securenative/EventOptionsBuilder.java new file mode 100644 index 0000000..4c8271e --- /dev/null +++ b/src/main/java/com/securenative/EventOptionsBuilder.java @@ -0,0 +1,76 @@ +package com.securenative; + + +import com.securenative.context.SecureNativeContext; +import com.securenative.enums.EventTypes; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.models.EventOptions; +import com.securenative.models.UserTraits; + +import java.util.Date; +import java.util.Map; + +public class EventOptionsBuilder { + private final int MAX_PROPERTIES_SIZE = 10; + private static final Logger logger = Logger.getLogger(EventOptionsBuilder.class); + private final EventOptions eventOptions; + + public static EventOptionsBuilder builder(String eventType) { + return new EventOptionsBuilder(eventType); + } + + public static EventOptionsBuilder builder(EventTypes eventType) { + return new EventOptionsBuilder(eventType.getType()); + } + + private EventOptionsBuilder(String eventType) { + eventOptions = new EventOptions(eventType); + } + + public EventOptionsBuilder userId(String userId) { + this.eventOptions.setUserId(userId); + return this; + } + + public EventOptionsBuilder userTraits(String name) { + this.eventOptions.setUserTraits(new UserTraits(name)); + return this; + } + + public EventOptionsBuilder userTraits(String name, String email) { + this.eventOptions.setUserTraits(new UserTraits(name, email)); + return this; + } + + public EventOptionsBuilder userTraits(String name, String email, String phone, Date createdAt) { + this.eventOptions.setUserTraits(new UserTraits(name, email, phone, createdAt)); + return this; + } + + public EventOptionsBuilder userTraits(String name, String email, String phone) { + this.eventOptions.setUserTraits(new UserTraits(name, email, phone)); + return this; + } + + public EventOptionsBuilder context(SecureNativeContext context) { + this.eventOptions.setContext(context); + return this; + } + + public EventOptionsBuilder properties(Map properties) { + this.eventOptions.setProperties(properties); + return this; + } + + public EventOptionsBuilder timestamp(Date timestamp) { + this.eventOptions.setTimestamp(timestamp); + return this; + } + + public EventOptions build() throws SecureNativeInvalidOptionsException { + if (this.eventOptions.getProperties() != null && this.eventOptions.getProperties().size() > MAX_PROPERTIES_SIZE) { + throw new SecureNativeInvalidOptionsException(String.format("You can have only up to %d custom properties", MAX_PROPERTIES_SIZE)); + } + return this.eventOptions; + } +} diff --git a/src/main/java/com/securenative/Logger.java b/src/main/java/com/securenative/Logger.java new file mode 100644 index 0000000..c946235 --- /dev/null +++ b/src/main/java/com/securenative/Logger.java @@ -0,0 +1,89 @@ +package com.securenative; + +import org.slf4j.LoggerFactory; + +enum LogLevel { + TRACE("trace"), + DEBUG("trace"), + INFO("info"), + WARN("warn"), + ERROR("error"); + + + private final String text; + + LogLevel(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } +} + +interface ILogger { + void trace(String var1, Object... var2); + + void debug(String var1, Object... var2); + + void info(String var1, Object... var2); + + void warn(String var1, Object... var2); + + void error(String var1, Object... var2); +} + +public class Logger implements ILogger { + private static LogLevel _logLevel = LogLevel.ERROR; + private org.slf4j.Logger _logger = null; + + private Logger(Class clazz) { + this._logger = LoggerFactory.getLogger(clazz); + } + + static void initLogger(String logLevel) { + try { + _logLevel = LogLevel.valueOf(logLevel.toLowerCase()); + } catch (IllegalArgumentException ignored) {} + } + + public static Logger getLogger(Class clazz) { + return new Logger(clazz); + } + + @Override + public void trace(String var1, Object... var2) { + if (_logLevel == LogLevel.TRACE) { + _logger.error(var1, var2); + } + } + + @Override + public void debug(String var1, Object... var2) { + if (_logLevel == LogLevel.DEBUG) { + _logger.debug(var1, var2); + } + } + + @Override + public void info(String var1, Object... var2) { + if (_logLevel == LogLevel.INFO) { + _logger.error(var1, var2); + } + } + + @Override + public void warn(String var1, Object... var2) { + if (_logLevel == LogLevel.WARN) { + _logger.warn(var1, var2); + } + } + + @Override + public void error(String var1, Object... var2) { + if (_logLevel == LogLevel.ERROR) { + _logger.error(var1, var2); + } + } +} diff --git a/src/main/java/com/securenative/Maps.java b/src/main/java/com/securenative/Maps.java new file mode 100644 index 0000000..9005050 --- /dev/null +++ b/src/main/java/com/securenative/Maps.java @@ -0,0 +1,31 @@ +package com.securenative; + +import java.util.HashMap; +import java.util.Map; + +public class Maps { + public static MapWrapper defaultBuilder() { + return new MapWrapper<>(); + } + + public static MapWrapper builder() { + return new MapWrapper(); + } + + public static final class MapWrapper { + private final HashMap map; + + public MapWrapper() { + map = new HashMap(); + } + + public MapWrapper put(K key, V value) { + map.put(key, value); + return this; + } + + public Map build() { + return map; + } + } +} diff --git a/src/main/java/com/securenative/ResourceStream.java b/src/main/java/com/securenative/ResourceStream.java new file mode 100644 index 0000000..997f985 --- /dev/null +++ b/src/main/java/com/securenative/ResourceStream.java @@ -0,0 +1,7 @@ +package com.securenative; + +import java.io.InputStream; + +public interface ResourceStream { + InputStream getInputStream(String name); +} \ No newline at end of file diff --git a/src/main/java/com/securenative/ResourceStreamImpl.java b/src/main/java/com/securenative/ResourceStreamImpl.java new file mode 100644 index 0000000..8e98d68 --- /dev/null +++ b/src/main/java/com/securenative/ResourceStreamImpl.java @@ -0,0 +1,10 @@ +package com.securenative; + +import java.io.InputStream; + +public class ResourceStreamImpl implements ResourceStream { + @Override + public InputStream getInputStream(String name) { + return SecureNative.class.getClassLoader().getResourceAsStream(name); + } +} diff --git a/src/main/java/com/securenative/SecureNative.java b/src/main/java/com/securenative/SecureNative.java new file mode 100644 index 0000000..bda3944 --- /dev/null +++ b/src/main/java/com/securenative/SecureNative.java @@ -0,0 +1,111 @@ +package com.securenative; + +import com.securenative.config.ConfigurationManager; +import com.securenative.config.SecureNativeConfigurationBuilder; +import com.securenative.config.SecureNativeOptions; +import com.securenative.context.SecureNativeContextBuilder; +import com.securenative.exceptions.SecureNativeConfigException; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.exceptions.SecureNativeSDKIllegalStateException; +import com.securenative.http.SecureNativeHTTPClient; +import com.securenative.models.EventOptions; +import com.securenative.models.VerifyResult; +import com.securenative.utils.SignatureUtils; +import com.securenative.utils.Utils; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.file.Path; +import java.util.stream.Collectors; + +import static com.securenative.utils.SignatureUtils.SIGNATURE_HEADER; + +public class SecureNative implements ApiManager { + public static final Logger logger = Logger.getLogger(SecureNative.class); + private static SecureNative secureNative = null; + private final ApiManager apiManager; + private final SecureNativeOptions options; + + private SecureNative(SecureNativeOptions options) throws SecureNativeSDKException { + if (Utils.isNullOrEmpty(options.getApiKey())) { + throw new SecureNativeSDKException("You must pass your SecureNative api key"); + } + this.options = options; + + EventManager eventManager = new SecureNativeEventManager(new SecureNativeHTTPClient(options), options); + if (options.getAutoSend()) { + eventManager.startEventsPersist(); + } + this.apiManager = new ApiManagerImpl(eventManager, options); + Logger.initLogger(options.getLogLevel()); + } + + public static SecureNative init(SecureNativeOptions options) throws SecureNativeSDKException { + if (secureNative == null) { + secureNative = new SecureNative(options); + return secureNative; + } + throw new SecureNativeSDKException("This SDK was already initialized"); + } + + public static SecureNative init(String apiKey) throws SecureNativeSDKException, SecureNativeConfigException { + if (Utils.isNullOrEmpty(apiKey)) { + throw new SecureNativeConfigException("You must pass your SecureNative api key"); + } + SecureNativeConfigurationBuilder builder = SecureNativeConfigurationBuilder.defaultConfigBuilder(); + SecureNativeOptions secureNativeOptions = builder.withApiKey(apiKey).build(); + return init(secureNativeOptions); + } + + + public static SecureNative init() throws SecureNativeSDKException, SecureNativeConfigException { + SecureNativeOptions secureNativeOptions = ConfigurationManager.loadConfig(); + return init(secureNativeOptions); + } + + public static SecureNative init(Path path) throws SecureNativeSDKException, SecureNativeConfigException { + SecureNativeOptions secureNativeOptions = ConfigurationManager.loadConfig(path); + return init(secureNativeOptions); + } + + public static SecureNative getInstance() throws SecureNativeSDKIllegalStateException { + if (secureNative == null) { + throw new SecureNativeSDKIllegalStateException(); + } + return secureNative; + } + + public SecureNativeOptions getOptions() { + return options; + } + + public static SecureNativeConfigurationBuilder configBuilder() { + return SecureNativeConfigurationBuilder.defaultConfigBuilder(); + } + + public static SecureNativeContextBuilder contextBuilder() { + return SecureNativeContextBuilder.defaultContextBuilder(); + } + + public SecureNativeContextBuilder fromHttpServletRequest(HttpServletRequest request) { + return SecureNativeContextBuilder.fromHttpServletRequest(request, this.options); + } + + public boolean verifyRequestPayload(HttpServletRequest request) throws IOException { + String requestSignature = request.getHeader(SIGNATURE_HEADER); + String body = request.getReader().lines().collect(Collectors.joining()); + + return SignatureUtils.isValidSignature(requestSignature, body, this.options.getApiKey()); + } + + @Override + public void track(EventOptions eventOptions) throws SecureNativeInvalidOptionsException { + this.apiManager.track(eventOptions); + } + + @Override + public VerifyResult verify(EventOptions eventOptions) throws SecureNativeInvalidOptionsException { + return this.apiManager.verify(eventOptions); + } +} \ No newline at end of file diff --git a/src/main/java/com/securenative/SecureNativeEventManager.java b/src/main/java/com/securenative/SecureNativeEventManager.java new file mode 100644 index 0000000..5cf3bc9 --- /dev/null +++ b/src/main/java/com/securenative/SecureNativeEventManager.java @@ -0,0 +1,145 @@ +package com.securenative; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.securenative.config.SecureNativeOptions; +import com.securenative.exceptions.SecureNativeHttpException; +import com.securenative.exceptions.SecureNativeParseException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.http.HttpClient; +import com.securenative.http.HttpResponse; +import com.securenative.models.Event; +import com.securenative.models.RequestOptions; + +import java.io.IOException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + + +public class SecureNativeEventManager implements EventManager { + private static final Logger logger = Logger.getLogger(SecureNativeEventManager.class); + private final int[] coefficients = new int[]{1, 1, 2, 3, 5, 8, 13}; + private int attempt = 0; + private Boolean sendEnabled = false; + private ScheduledExecutorService scheduler; + private final ConcurrentLinkedQueue events; + private final ObjectMapper mapper; + private final SecureNativeOptions options; + private final HttpClient httpClient; + + public SecureNativeEventManager(HttpClient httpClient, SecureNativeOptions options) throws SecureNativeSDKException { + this.options = options; + this.httpClient = httpClient; + + this.events = new ConcurrentLinkedQueue<>(); + mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public T sendSync(Class clazz, Event event, String url) throws IOException, SecureNativeParseException { + if (this.options.getDisabled()) { + logger.warn("SDK is disabled, no operation will be performed"); + return null; + } + + String body = mapper.writeValueAsString(event); + logger.debug("Attempting to send event", body); + HttpResponse response = this.httpClient.post(url, body); + if (!response.isOk()) { + logger.info(String.format("Secure Native http call failed to end point: %s with event type %s. adding back to queue.", url, event.getEventType())); + throw new IOException(String.valueOf(response.getStatusCode())); + } + + String responseBody = response.getBody(); + try { + return mapper.readValue(responseBody, clazz); + } catch (Exception ex) { + logger.error("Failed to parse response body", ex.getMessage()); + throw new SecureNativeParseException(ex.getMessage()); + } + } + + + @Override + public void sendAsync(Event event, String url, Boolean retry) { + if (this.options.getDisabled()) { + return; + } + + try { + String body = mapper.writeValueAsString(event); + this.events.add(new RequestOptions(url, body, retry)); + } catch (JsonProcessingException e) { + logger.error("Failed to deserialize event", e.getMessage()); + } + } + + private void sendEvents() throws InterruptedException { + if (!this.events.isEmpty() && this.sendEnabled) { + RequestOptions requestOptions = events.peek(); + try { + String body = requestOptions.getBody(); + HttpResponse resp = this.httpClient.post(requestOptions.getUrl(), body); + if (resp.getStatusCode() == 401) { + requestOptions.setRetry(false); + } + if (!resp.isOk()) { + throw new SecureNativeHttpException(String.valueOf(resp.getStatusCode())); + } + logger.debug("Event successfully sent", body); + // remove the event from queue + events.remove(requestOptions); + } catch (Exception ex) { + logger.error("Failed to send event", ex.getMessage()); + if (requestOptions.getRetry()) { + if (attempt++ == coefficients.length) { + attempt = 0; + } + int backoff = coefficients[attempt] * this.options.getInterval(); + logger.debug("BackOff automatic sending by", backoff); + this.sendEnabled = false; + Thread.sleep(backoff); + this.sendEnabled = true; + } else { + // remove the event from queue, retry: false + events.remove(requestOptions); + } + } + } + } + + public void startEventsPersist() { + logger.debug("Starting automatic event persistence"); + if (!this.options.getAutoSend() || this.sendEnabled) { + logger.debug("Automatic event persistence disabled, you should manually persist events"); + return; + } + + this.sendEnabled = true; + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + this.scheduler.scheduleWithFixedDelay(() -> { + try { + sendEvents(); + } catch (InterruptedException ignored) { + } + }, 0, this.options.getInterval(), TimeUnit.MILLISECONDS); + } + + public void stopEventsPersist() { + if (this.sendEnabled) { + logger.debug("Attempting to stop automatic event persistence"); + + try { + this.scheduler.shutdown(); + // drain event queue + this.scheduler.awaitTermination(this.options.getTimeout(), TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { + } + + logger.debug("Stopped event persistence"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/securenative/config/ConfigurationManager.java b/src/main/java/com/securenative/config/ConfigurationManager.java new file mode 100644 index 0000000..2bea82c --- /dev/null +++ b/src/main/java/com/securenative/config/ConfigurationManager.java @@ -0,0 +1,114 @@ +package com.securenative.config; + +import com.securenative.ResourceStream; +import com.securenative.ResourceStreamImpl; +import com.securenative.SecureNative; +import com.securenative.enums.FailoverStrategy; +import com.securenative.utils.Utils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; +import java.util.Properties; + +public class ConfigurationManager { + private static final String DEFAULT_CONFIG_FILE = "securenative.properties"; + private static final String CUSTOM_CONFIG_FILE_ENV_NAME = "SECURENATIVE_CONFIG_FILE"; + private static ResourceStream resourceStream = new ResourceStreamImpl(); + + private static String getEnvOrDefault(String envName, String defaultValue) { + String envValue = System.getenv(envName); + if (envValue != null) { + return envValue; + } + + String propValue = System.getProperty(envName); + if (propValue != null) { + return propValue; + } + return defaultValue; + } + + public static void setResourceStream(ResourceStream resourceStream) { + ConfigurationManager.resourceStream = resourceStream; + } + + private static Properties loadProperties(Properties properties, InputStream inputStream) { + try (final InputStream stream = inputStream) { + properties.load(stream); + } catch (Exception e) { + return new Properties(); + } + return properties; + } + + private static Properties readResourceFile(String resourcePath) { + Properties properties = new Properties(); + URL resourceUrl = SecureNative.class.getClassLoader().getResource(resourcePath); + if (resourceUrl != null) { + InputStream resourceStream = ConfigurationManager.resourceStream.getInputStream(resourcePath); + properties = loadProperties(properties, resourceStream); + } + return properties; + } + + private static String getPropertyOrEnvOrDefault(Properties properties, String key, Object defaultValue) { + String defaultStrValue = defaultValue == null ? null : defaultValue.toString(); + Object res = properties.getOrDefault(key, getEnvOrDefault(key, defaultStrValue)); + return res == null ? null : res.toString(); + } + + private static ArrayList getPropertyListOrEnvOrDefault(Properties properties, String key, Object defaultValue) { + String defaultStrValue = defaultValue == null ? null : defaultValue.toString(); + Object res = properties.getOrDefault(key, getEnvOrDefault(key, defaultStrValue)); + return res == null ? null : new ArrayList<>(Arrays.asList(res.toString().split(","))); + } + + public static SecureNativeConfigurationBuilder configBuilder() { + return SecureNativeConfigurationBuilder.defaultConfigBuilder(); + } + + public static SecureNativeOptions loadConfig() { + String resourceFilePath = getEnvOrDefault(CUSTOM_CONFIG_FILE_ENV_NAME, DEFAULT_CONFIG_FILE); + Properties properties = readResourceFile(resourceFilePath); + return getOptions(properties); + } + + public static SecureNativeOptions loadConfig(Path path) { + Properties properties = new Properties(); + InputStream input; + try { + input = new FileInputStream(path.toString()); + properties.load(input); + } catch (IOException ignore) { + + } + + return getOptions(properties); + } + + private static SecureNativeOptions getOptions(Properties properties) { + SecureNativeConfigurationBuilder builder = SecureNativeConfigurationBuilder.defaultConfigBuilder(); + SecureNativeOptions defaultOptions = builder.build(); + + builder.withApiKey(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_API_KEY", defaultOptions.getApiKey())) + .withApiUrl(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_API_URL", defaultOptions.getApiUrl())) + .withInterval(Utils.parseIntegerOrDefault(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_INTERVAL", defaultOptions.getInterval()), defaultOptions.getInterval())) + .withMaxEvents(Utils.parseIntegerOrDefault(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_MAX_EVENTS", defaultOptions.getMaxEvents()), defaultOptions.getMaxEvents())) + .withTimeout(Utils.parseIntegerOrDefault(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_TIMEOUT", defaultOptions.getTimeout()), defaultOptions.getTimeout())) + .withAutoSend(Utils.parseBooleanOrDefault(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_AUTO_SEND", defaultOptions.getAutoSend()), defaultOptions.getAutoSend())) + .withDisable(Utils.parseBooleanOrDefault(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_DISABLE", defaultOptions.getDisabled()), defaultOptions.getDisabled())) + .withLogLevel(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_LOG_LEVEL", defaultOptions.getLogLevel())) + .withFailoverStrategy(FailoverStrategy.fromString(Objects.requireNonNull(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_FAILOVER_STRATEGY", defaultOptions.getFailoverStrategy())), defaultOptions.getFailoverStrategy())) + .withProxyHeaders(getPropertyListOrEnvOrDefault(properties, "SECURENATIVE_PROXY_HEADERS", defaultOptions.getProxyHeaders())) + .withPiiHeaders(getPropertyListOrEnvOrDefault(properties, "SECURENATIVE_PII_HEADERS", defaultOptions.getPiiHeaders())) + .withPiiRegexPattern(getPropertyOrEnvOrDefault(properties, "SECURENATIVE_PII_REGEX_PATTERN", defaultOptions.getPiiRegexPattern())); + return builder.build(); + } +} + diff --git a/src/main/java/com/securenative/config/SecureNativeConfigurationBuilder.java b/src/main/java/com/securenative/config/SecureNativeConfigurationBuilder.java new file mode 100644 index 0000000..2b93d76 --- /dev/null +++ b/src/main/java/com/securenative/config/SecureNativeConfigurationBuilder.java @@ -0,0 +1,150 @@ +package com.securenative.config; + +import com.securenative.enums.FailoverStrategy; + +import java.util.ArrayList; + +public class SecureNativeConfigurationBuilder { + /** + * Api Secret associated with SecureNative account + */ + private String apiKey; + + /** + * SecureNative backend API URL + */ + private String apiUrl; + + /** + * SecureNative event persistence interval + */ + private int interval; + + /** + * Maximum queue capacity + */ + private int maxEvents; + + /** + * Event sending timeout + */ + private int timeout; + + /** + * Allow automatically track event + */ + private Boolean autoSend; + + /** + * Disable SDk, all operation will not take effect + */ + private Boolean disable; + + /** + * Default log level + */ + private String logLevel; + + /** + * Failover strategy + */ + private FailoverStrategy failoverStrategy; + + /** + * Proxy Headers + */ + private ArrayList proxyHeaders; + + /** + * Pii Headers + */ + private ArrayList piiHeaders; + + /** + * Pii Regex Pattern + */ + private String piiRegexPattern; + + private SecureNativeConfigurationBuilder() { + } + + public static SecureNativeConfigurationBuilder defaultConfigBuilder() { + return new SecureNativeConfigurationBuilder() + .withApiKey(null) + .withApiUrl("https://api.securenative.com/collector/api/v1") + .withInterval(1000) + .withTimeout(1500) + .withMaxEvents(1000) + .withAutoSend(true) + .withDisable(false) + .withLogLevel("fatal") + .withFailoverStrategy(FailoverStrategy.FAIL_OPEN) + .withProxyHeaders(new ArrayList<>()) + .withPiiHeaders(new ArrayList<>()) + .withPiiRegexPattern(null); + } + + public SecureNativeConfigurationBuilder withApiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public SecureNativeConfigurationBuilder withApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + return this; + } + + public SecureNativeConfigurationBuilder withInterval(int interval) { + this.interval = interval; + return this; + } + + public SecureNativeConfigurationBuilder withMaxEvents(int maxEvents) { + this.maxEvents = maxEvents; + return this; + } + + public SecureNativeConfigurationBuilder withTimeout(int timeout) { + this.timeout = timeout; + return this; + } + + public SecureNativeConfigurationBuilder withAutoSend(Boolean autoSend) { + this.autoSend = autoSend; + return this; + } + + public SecureNativeConfigurationBuilder withDisable(Boolean disable) { + this.disable = disable; + return this; + } + + public SecureNativeConfigurationBuilder withLogLevel(String logLevel) { + this.logLevel = logLevel; + return this; + } + + public SecureNativeConfigurationBuilder withFailoverStrategy(FailoverStrategy failoverStrategy) { + this.failoverStrategy = failoverStrategy; + return this; + } + + public SecureNativeConfigurationBuilder withProxyHeaders(ArrayList proxyHeaders) { + this.proxyHeaders = proxyHeaders; + return this; + } + + public SecureNativeConfigurationBuilder withPiiHeaders(ArrayList piiHeaders) { + this.piiHeaders = piiHeaders; + return this; + } + + public SecureNativeConfigurationBuilder withPiiRegexPattern(String piiRegexPattern) { + this.piiRegexPattern = piiRegexPattern; + return this; + } + + public SecureNativeOptions build() { + return new SecureNativeOptions(apiKey, apiUrl, interval, maxEvents, timeout, autoSend, disable, logLevel, failoverStrategy, proxyHeaders, piiHeaders, piiRegexPattern); + } +} diff --git a/src/main/java/com/securenative/config/SecureNativeOptions.java b/src/main/java/com/securenative/config/SecureNativeOptions.java new file mode 100644 index 0000000..1967368 --- /dev/null +++ b/src/main/java/com/securenative/config/SecureNativeOptions.java @@ -0,0 +1,130 @@ +package com.securenative.config; + +import com.securenative.enums.FailoverStrategy; + +import java.util.ArrayList; + +public class SecureNativeOptions { + /** + * Api Secret associated with SecureNative account + */ + private final String apiKey; + + /** + * SecureNative backend API URL + */ + private final String apiUrl; + + /** + * SecureNative event persistence interval + */ + private final int interval; + + /** + * Maximum queue capacity + */ + private final int maxEvents; + + /** + * Event sending timeout + */ + private final int timeout; + + /** + * Allow automatically track event + */ + private final Boolean autoSend; + + /** + * Disable SDk, all operation will not take effect + */ + private final Boolean disable; + + /** + * Default log level + */ + private final String logLevel; + + /** + * Failover strategy + */ + private final FailoverStrategy failoverStrategy; + + /** + * Proxy Headers + */ + private final ArrayList proxyHeaders; + + /** + * Pii Headers + */ + private final ArrayList piiHeaders; + + /** + * Pii Regex Pattern + */ + private final String piiRegexPattern; + + public SecureNativeOptions(String apiKey, String apiUrl, int interval, int maxEvents, int timeout, boolean autoSend, boolean disable, String logLevel, FailoverStrategy failoverStrategy, ArrayList proxyHeaders, ArrayList piiHeaders, String piiRegexPattern) { + this.apiKey = apiKey; + this.apiUrl = apiUrl; + this.interval = interval; + this.maxEvents = maxEvents; + this.timeout = timeout; + this.autoSend = autoSend; + this.disable = disable; + this.logLevel = logLevel; + this.failoverStrategy = failoverStrategy; + this.proxyHeaders = proxyHeaders; + this.piiHeaders = piiHeaders; + this.piiRegexPattern = piiRegexPattern; + } + + public String getApiKey() { + return apiKey; + } + + public String getApiUrl() { + return apiUrl; + } + + public int getInterval() { + return interval; + } + + public int getMaxEvents() { + return maxEvents; + } + + public int getTimeout() { + return timeout; + } + + public Boolean getAutoSend() { + return autoSend; + } + + public Boolean getDisabled() { + return disable; + } + + public String getLogLevel() { + return logLevel; + } + + public FailoverStrategy getFailoverStrategy() { + return failoverStrategy; + } + + public ArrayList getProxyHeaders() { + return proxyHeaders; + } + + public ArrayList getPiiHeaders() { + return piiHeaders; + } + + public String getPiiRegexPattern() { + return piiRegexPattern; + } +} diff --git a/src/main/java/com/securenative/context/SecureNativeContext.java b/src/main/java/com/securenative/context/SecureNativeContext.java new file mode 100644 index 0000000..436867d --- /dev/null +++ b/src/main/java/com/securenative/context/SecureNativeContext.java @@ -0,0 +1,82 @@ +package com.securenative.context; + +import java.util.Map; + +public class SecureNativeContext { + private String clientToken; + private String ip; + private String remoteIp; + private Map headers; + private String url; + private String method; + private String body; + + public SecureNativeContext() { + } + + public SecureNativeContext(String clientToken, String ip, String remoteIp, Map headers, String url, String method, String body) { + this.clientToken = clientToken; + this.ip = ip; + this.remoteIp = remoteIp; + this.headers = headers; + this.url = url; + this.method = method; + this.body = body; + } + + public String getClientToken() { + return clientToken; + } + + public void setClientToken(String clientToken) { + this.clientToken = clientToken; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/src/main/java/com/securenative/context/SecureNativeContextBuilder.java b/src/main/java/com/securenative/context/SecureNativeContextBuilder.java new file mode 100644 index 0000000..86e44bf --- /dev/null +++ b/src/main/java/com/securenative/context/SecureNativeContextBuilder.java @@ -0,0 +1,78 @@ +package com.securenative.context; + +import com.securenative.config.SecureNativeOptions; +import com.securenative.utils.RequestUtils; +import com.securenative.utils.Utils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +public class SecureNativeContextBuilder { + private final SecureNativeContext context; + + private SecureNativeContextBuilder() { + this.context = new SecureNativeContext(); + } + + public SecureNativeContextBuilder withClientToken(String clientToken) { + this.context.setClientToken(clientToken); + return this; + } + + public SecureNativeContextBuilder withIp(String ip) { + this.context.setIp(ip); + return this; + } + + public SecureNativeContextBuilder withRemoteIp(String remoteIp) { + this.context.setRemoteIp(remoteIp); + return this; + } + + public SecureNativeContextBuilder withHeaders(Map headers) { + this.context.setHeaders(headers); + return this; + } + + public SecureNativeContextBuilder withUrl(String url) { + this.context.setUrl(url); + return this; + } + + public SecureNativeContextBuilder withMethod(String method) { + this.context.setMethod(method); + return this; + } + + public SecureNativeContextBuilder withBody(String body) { + this.context.setBody(body); + return this; + } + + public static SecureNativeContextBuilder defaultContextBuilder() { + return new SecureNativeContextBuilder(); + } + + public static SecureNativeContextBuilder fromHttpServletRequest(HttpServletRequest request, SecureNativeOptions options) { + Map headers = RequestUtils.getHeadersFromRequest(request, options); + + String clientToken = RequestUtils.getCookieValueFromRequest(request, RequestUtils.SECURENATIVE_COOKIE); + if (Utils.isNullOrEmpty(clientToken)) { + clientToken = RequestUtils.getSecureHeaderFromRequest(headers); + } + + return new SecureNativeContextBuilder() + .withUrl(request.getRequestURI()) + .withMethod(request.getMethod()) + .withHeaders(headers) + .withClientToken(clientToken) + .withIp(RequestUtils.getClientIpFromRequest(request, headers, options)) + .withRemoteIp(RequestUtils.getRemoteIpFromRequest(request)) + .withBody(null); + } + + public SecureNativeContext build() { + return this.context; + } +} + diff --git a/src/main/java/com/securenative/enums/ApiRoute.java b/src/main/java/com/securenative/enums/ApiRoute.java new file mode 100644 index 0000000..1a262bc --- /dev/null +++ b/src/main/java/com/securenative/enums/ApiRoute.java @@ -0,0 +1,16 @@ +package com.securenative.enums; + +public enum ApiRoute { + TRACK("track"), + VERIFY("verify"); + + private final String apiRoute; + + public String getApiRoute() { + return apiRoute; + } + + ApiRoute(String apiRoute) { + this.apiRoute = apiRoute; + } +} \ No newline at end of file diff --git a/sdk-base/src/main/java/com/securenative/models/EventTypes.java b/src/main/java/com/securenative/enums/EventTypes.java similarity index 91% rename from sdk-base/src/main/java/com/securenative/models/EventTypes.java rename to src/main/java/com/securenative/enums/EventTypes.java index 8fdec8e..176dd45 100644 --- a/sdk-base/src/main/java/com/securenative/models/EventTypes.java +++ b/src/main/java/com/securenative/enums/EventTypes.java @@ -1,4 +1,6 @@ -package com.securenative.models; +package com.securenative.enums; + +import com.fasterxml.jackson.annotation.JsonValue; public enum EventTypes { LOG_IN("sn.user.login"), @@ -21,6 +23,7 @@ public enum EventTypes { PAGE_VIEW("sn.user.page.view"), VERIFY("sn.verify"); + @JsonValue public String getType() { return type; } diff --git a/src/main/java/com/securenative/enums/FailoverStrategy.java b/src/main/java/com/securenative/enums/FailoverStrategy.java new file mode 100644 index 0000000..a4f6070 --- /dev/null +++ b/src/main/java/com/securenative/enums/FailoverStrategy.java @@ -0,0 +1,27 @@ +package com.securenative.enums; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum FailoverStrategy { + FAIL_OPEN("fail-open"), + FAIL_CLOSED("fail-closed"); + + private final String failoverStrategy; + + @JsonValue + public String getFailoverStrategy() { + return failoverStrategy; + } + + public static FailoverStrategy fromString(String key, FailoverStrategy failoverStrategy) { + try { + return FailoverStrategy.valueOf(key.replace("-", "_").toUpperCase()); + } catch (IllegalArgumentException ex) { + return failoverStrategy; + } + } + + FailoverStrategy(String failoverStrategy) { + this.failoverStrategy = failoverStrategy; + } +} \ No newline at end of file diff --git a/src/main/java/com/securenative/enums/RiskLevel.java b/src/main/java/com/securenative/enums/RiskLevel.java new file mode 100644 index 0000000..57ca1dd --- /dev/null +++ b/src/main/java/com/securenative/enums/RiskLevel.java @@ -0,0 +1,27 @@ +package com.securenative.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum RiskLevel { + LOW("low"), + MEDIUM("medium"), + HIGH("high"); + + private final String riskLevel; + + @JsonValue + public String getRiskLevel() { + return riskLevel; + } + + @JsonCreator + public static RiskLevel fromString(String key) { + return key == null ? null : RiskLevel.valueOf(key.toUpperCase()); + } + + RiskLevel(String riskLevel) { + this.riskLevel = riskLevel; + } + +} \ No newline at end of file diff --git a/src/main/java/com/securenative/exceptions/SecureNativeConfigException.java b/src/main/java/com/securenative/exceptions/SecureNativeConfigException.java new file mode 100644 index 0000000..b6e05ba --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeConfigException.java @@ -0,0 +1,8 @@ +package com.securenative.exceptions; + +public class SecureNativeConfigException extends Exception { + public SecureNativeConfigException(String message) { + super(message); + } +} + diff --git a/src/main/java/com/securenative/exceptions/SecureNativeHttpException.java b/src/main/java/com/securenative/exceptions/SecureNativeHttpException.java new file mode 100644 index 0000000..3bad2fe --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeHttpException.java @@ -0,0 +1,7 @@ +package com.securenative.exceptions; + +public class SecureNativeHttpException extends Exception { + public SecureNativeHttpException(String message) { + super(message); + } +} diff --git a/src/main/java/com/securenative/exceptions/SecureNativeInvalidOptionsException.java b/src/main/java/com/securenative/exceptions/SecureNativeInvalidOptionsException.java new file mode 100644 index 0000000..30a00eb --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeInvalidOptionsException.java @@ -0,0 +1,7 @@ +package com.securenative.exceptions; + +public class SecureNativeInvalidOptionsException extends Exception { + public SecureNativeInvalidOptionsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/securenative/exceptions/SecureNativeInvalidUriException.java b/src/main/java/com/securenative/exceptions/SecureNativeInvalidUriException.java new file mode 100644 index 0000000..931a78c --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeInvalidUriException.java @@ -0,0 +1,8 @@ +package com.securenative.exceptions; + +public class SecureNativeInvalidUriException extends Exception { + public SecureNativeInvalidUriException(String message) { + super(message); + } +} + diff --git a/src/main/java/com/securenative/exceptions/SecureNativeParseException.java b/src/main/java/com/securenative/exceptions/SecureNativeParseException.java new file mode 100644 index 0000000..bfef86c --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeParseException.java @@ -0,0 +1,7 @@ +package com.securenative.exceptions; + +public class SecureNativeParseException extends Exception { + public SecureNativeParseException(String message) { + super(message); + } +} diff --git a/sdk-base/src/main/java/com/securenative/exceptions/SecureNativeSDKException.java b/src/main/java/com/securenative/exceptions/SecureNativeSDKException.java similarity index 98% rename from sdk-base/src/main/java/com/securenative/exceptions/SecureNativeSDKException.java rename to src/main/java/com/securenative/exceptions/SecureNativeSDKException.java index a731873..9bc0c05 100644 --- a/sdk-base/src/main/java/com/securenative/exceptions/SecureNativeSDKException.java +++ b/src/main/java/com/securenative/exceptions/SecureNativeSDKException.java @@ -5,3 +5,5 @@ public SecureNativeSDKException(String message) { super(message); } } + + diff --git a/src/main/java/com/securenative/exceptions/SecureNativeSDKIllegalStateException.java b/src/main/java/com/securenative/exceptions/SecureNativeSDKIllegalStateException.java new file mode 100644 index 0000000..2972e04 --- /dev/null +++ b/src/main/java/com/securenative/exceptions/SecureNativeSDKIllegalStateException.java @@ -0,0 +1,7 @@ +package com.securenative.exceptions; + +public class SecureNativeSDKIllegalStateException extends SecureNativeSDKException { + public SecureNativeSDKIllegalStateException() { + super("Secure Native SDK wasn't initialized yet, please call init first"); + } +} diff --git a/src/main/java/com/securenative/http/HttpClient.java b/src/main/java/com/securenative/http/HttpClient.java new file mode 100644 index 0000000..04390ee --- /dev/null +++ b/src/main/java/com/securenative/http/HttpClient.java @@ -0,0 +1,8 @@ +package com.securenative.http; + +import java.io.IOException; + +public interface HttpClient { + HttpResponse post(String url, String body) throws IOException; +} + diff --git a/src/main/java/com/securenative/http/HttpResponse.java b/src/main/java/com/securenative/http/HttpResponse.java new file mode 100644 index 0000000..ec1bc41 --- /dev/null +++ b/src/main/java/com/securenative/http/HttpResponse.java @@ -0,0 +1,25 @@ +package com.securenative.http; + +public class HttpResponse { + private final Boolean ok; + private final int statusCode; + private final String body; + + public HttpResponse(Boolean ok, int statusCode, String body) { + this.ok = ok; + this.statusCode = statusCode; + this.body = body; + } + + public Boolean isOk() { + return ok; + } + + public int getStatusCode() { + return statusCode; + } + + public String getBody() { + return body; + } +} diff --git a/src/main/java/com/securenative/http/SecureNativeHTTPClient.java b/src/main/java/com/securenative/http/SecureNativeHTTPClient.java new file mode 100644 index 0000000..9ea9204 --- /dev/null +++ b/src/main/java/com/securenative/http/SecureNativeHTTPClient.java @@ -0,0 +1,53 @@ +package com.securenative.http; + +import com.securenative.config.SecureNativeOptions; +import com.securenative.utils.VersionUtils; +import okhttp3.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +public class SecureNativeHTTPClient implements HttpClient { + private final String AUTHORIZATION_HEADER = "Authorization"; + private final String VERSION_HEADER = "SN-Version"; + private final String USER_AGENT_HEADER = "User-Agent"; + private final String USER_AGENT_HEADER_VALUE = "SecureNative-java"; + private final OkHttpClient client; + private final SecureNativeOptions options; + public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); + + public SecureNativeHTTPClient(SecureNativeOptions options) { + this.options = options; + this.client = new OkHttpClient + .Builder() + .readTimeout(options.getTimeout(), TimeUnit.MILLISECONDS) + .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT)) + .addInterceptor((chain) -> { + Request request = chain.request(); + Request authenticatedRequest = request.newBuilder() + .header(USER_AGENT_HEADER, USER_AGENT_HEADER_VALUE) + .header(VERSION_HEADER, VersionUtils.getVersion()) + .header(AUTHORIZATION_HEADER, options.getApiKey()).build(); + return chain.proceed(authenticatedRequest); + }).build(); + } + + + @Override + public HttpResponse post(String path, String payload) throws IOException { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, payload); + + String url = String.format("%s/%s", this.options.getApiUrl(), path); + + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + try (Response response = this.client.newCall(request).execute()) { + int statusCode = response.code(); + String responseBody = response.body().string(); + return new HttpResponse(response.isSuccessful(), statusCode, responseBody); + } + } +} diff --git a/sdk-base/src/main/java/com/securenative/models/ClientFingerPrint.java b/src/main/java/com/securenative/models/ClientToken.java similarity index 58% rename from sdk-base/src/main/java/com/securenative/models/ClientFingerPrint.java rename to src/main/java/com/securenative/models/ClientToken.java index 932ef22..88918a1 100644 --- a/sdk-base/src/main/java/com/securenative/models/ClientFingerPrint.java +++ b/src/main/java/com/securenative/models/ClientToken.java @@ -1,17 +1,21 @@ package com.securenative.models; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class ClientFingerPrint { +public class ClientToken { private String cid; + private String vid; private String fp; + public ClientToken() { + } + @JsonCreator - public ClientFingerPrint(@JsonProperty("cid") String cid, @JsonProperty("fp") String fp) { + public ClientToken(@JsonProperty("cid") String cid, @JsonProperty("vid") String vid, @JsonProperty("fp") String fp) { this.cid = cid; this.fp = fp; + this.vid = vid; } public String getCid() { @@ -22,6 +26,14 @@ public void setCid(String cid) { this.cid = cid; } + public String getVid() { + return vid; + } + + public void setVid(String vid) { + this.vid = vid; + } + public String getFp() { return fp; } diff --git a/sdk-base/src/main/java/com/securenative/models/Device.java b/src/main/java/com/securenative/models/Device.java similarity index 100% rename from sdk-base/src/main/java/com/securenative/models/Device.java rename to src/main/java/com/securenative/models/Device.java diff --git a/src/main/java/com/securenative/models/Event.java b/src/main/java/com/securenative/models/Event.java new file mode 100644 index 0000000..d6ee3ae --- /dev/null +++ b/src/main/java/com/securenative/models/Event.java @@ -0,0 +1,7 @@ +package com.securenative.models; + +public interface Event { + String getEventType(); + + String getTimestamp(); +} diff --git a/src/main/java/com/securenative/models/EventOptions.java b/src/main/java/com/securenative/models/EventOptions.java new file mode 100644 index 0000000..8f320ee --- /dev/null +++ b/src/main/java/com/securenative/models/EventOptions.java @@ -0,0 +1,67 @@ +package com.securenative.models; + +import com.securenative.context.SecureNativeContext; + +import java.util.Date; +import java.util.Map; + +public class EventOptions { + private String event; + private String userId; + private UserTraits userTraits; + private SecureNativeContext context; + private Map properties; + private Date timestamp; + + public EventOptions(String event) { + this.event = event; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public UserTraits getUserTraits() { + return userTraits; + } + + public void setUserTraits(UserTraits userTraits) { + this.userTraits = userTraits; + } + + public SecureNativeContext getContext() { + return context; + } + + public void setContext(SecureNativeContext context) { + this.context = context; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } +} diff --git a/src/main/java/com/securenative/models/RequestContext.java b/src/main/java/com/securenative/models/RequestContext.java new file mode 100644 index 0000000..ac57b05 --- /dev/null +++ b/src/main/java/com/securenative/models/RequestContext.java @@ -0,0 +1,117 @@ +package com.securenative.models; + +import java.util.Map; + +public class RequestContext { + private String cid; + private String vid; + private String fp; + private String ip; + private String remoteIp; + private Map headers; + private String url; + private String method; + + public RequestContext() { + } + + public RequestContext(String cid, String vid, String fp, String ip, String remoteIp, Map headers, String url, String method) { + this.cid = cid; + this.vid = vid; + this.fp = fp; + this.ip = ip; + this.remoteIp = remoteIp; + this.headers = headers; + this.url = url; + this.method = method; + } + + public String getCid() { + return cid; + } + + public String getVid() { + return vid; + } + + public String getFp() { + return fp; + } + + public String getIp() { + return ip; + } + + public String getRemoteIp() { + return remoteIp; + } + + public Map getHeaders() { + return headers; + } + + public String getUrl() { + return url; + } + + public String getMethod() { + return method; + } + + public static class RequestContextBuilder { + private String cid; + private String vid; + private String fp; + private String ip; + private String remoteIp; + private Map headers; + private String url; + private String method; + + public RequestContextBuilder withCid(String cid) { + this.cid = cid; + return this; + } + + public RequestContextBuilder withVid(String vid) { + this.vid = vid; + return this; + } + + public RequestContextBuilder withFp(String fp) { + this.fp = fp; + return this; + } + + public RequestContextBuilder withIp(String ip) { + this.ip = ip; + return this; + } + + public RequestContextBuilder withRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + return this; + } + + public RequestContextBuilder witHeaders(Map headers) { + this.headers = headers; + return this; + } + + public RequestContextBuilder withUrl(String url) { + this.url = url; + return this; + } + + public RequestContextBuilder withMethod(String method) { + this.method = method; + return this; + } + + public RequestContext build() { + return new RequestContext(cid, vid, fp, ip, remoteIp, headers, url, method); + } + } + +} + diff --git a/src/main/java/com/securenative/models/RequestOptions.java b/src/main/java/com/securenative/models/RequestOptions.java new file mode 100644 index 0000000..2be7f91 --- /dev/null +++ b/src/main/java/com/securenative/models/RequestOptions.java @@ -0,0 +1,29 @@ +package com.securenative.models; + +public class RequestOptions { + private final String url; + private final String body; + private Boolean retry; + + public RequestOptions(String url, String body, Boolean retry) { + this.url = url; + this.body = body; + this.retry = retry; + } + + public String getUrl() { + return url; + } + + public String getBody() { + return body; + } + + public Boolean getRetry() { + return retry; + } + + public void setRetry(Boolean retry) { + this.retry = retry; + } +} diff --git a/src/main/java/com/securenative/models/SDKEvent.java b/src/main/java/com/securenative/models/SDKEvent.java new file mode 100644 index 0000000..a5d3eca --- /dev/null +++ b/src/main/java/com/securenative/models/SDKEvent.java @@ -0,0 +1,98 @@ +package com.securenative.models; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.securenative.Logger; +import com.securenative.SecureNative; +import com.securenative.config.SecureNativeOptions; +import com.securenative.context.SecureNativeContext; +import com.securenative.context.SecureNativeContextBuilder; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.utils.DateUtils; +import com.securenative.utils.EncryptionUtils; + +import java.util.*; + +public class SDKEvent implements Event { + private final String rid; + public String eventType; + public String userId; + private final UserTraits userTraits; + public RequestContext request; + public String timestamp; + public Map properties; + public static final Logger logger = Logger.getLogger(SecureNative.class); + + public SDKEvent(EventOptions event, SecureNativeOptions options) throws SecureNativeInvalidOptionsException { + if (event.getUserId() == null || event.getUserId().length() <= 0 || event.getUserId().equals("")) { + throw new SecureNativeInvalidOptionsException("Invalid event structure; User Id is missing"); + } + + if (event.getEvent() == null || event.getEvent().length() <= 0 || event.getEvent().equals("")) { + throw new SecureNativeInvalidOptionsException("Invalid event structure; Event Type is missing"); + } + + SecureNativeContext context = event.getContext() != null ? event.getContext() : SecureNativeContextBuilder.defaultContextBuilder().build(); + + ClientToken clientToken = decryptToken(context.getClientToken(), options.getApiKey()); + + this.rid = UUID.randomUUID().toString(); + this.eventType = event.getEvent(); + this.userId = event.getUserId(); + this.userTraits = event.getUserTraits(); + this.request = new RequestContext.RequestContextBuilder() + .withCid(clientToken.getCid()) + .withVid(clientToken.getVid()) + .withFp(clientToken.getFp()) + .withIp(context.getIp()) + .withRemoteIp(context.getRemoteIp()) + .withMethod(context.getMethod()) + .withUrl(context.getUrl()) + .witHeaders(context.getHeaders()) + .build(); + this.timestamp = DateUtils.toTimestamp(event.getTimestamp()); + this.properties = event.getProperties(); + } + + private ClientToken decryptToken(String token, String key) { + if (token == null || token.length() == 0) { + return new ClientToken(); + } + ObjectMapper mapper = new ObjectMapper(); + try { + String decryptedClientToken = EncryptionUtils.decrypt(token, key); + return mapper.readValue(decryptedClientToken, ClientToken.class); + } catch (Exception ex) { + logger.error("Failed to decrypt token"); + } + return new ClientToken(); + } + + @Override + public String getEventType() { + return this.eventType; + } + + public String getRid() { + return rid; + } + + public String getUserId() { + return userId; + } + + public UserTraits getUserTraits() { + return userTraits; + } + + public RequestContext getRequest() { + return request; + } + + public String getTimestamp() { + return timestamp; + } + + public Map getProperties() { + return properties; + } +} \ No newline at end of file diff --git a/src/main/java/com/securenative/models/UserTraits.java b/src/main/java/com/securenative/models/UserTraits.java new file mode 100644 index 0000000..9269068 --- /dev/null +++ b/src/main/java/com/securenative/models/UserTraits.java @@ -0,0 +1,66 @@ +package com.securenative.models; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.util.Date; + +public class UserTraits { + private String name; + private String email; + private String phone; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private Date createdAt; + + public UserTraits(String name) { + this(name, null, null); + } + + public UserTraits(String name, String email) { + this(name, email, null); + } + + public UserTraits(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + public UserTraits(String name, String email, String phone, Date createdAt) { + this.name = name; + this.email = email; + this.phone = phone; + this.createdAt = createdAt; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} diff --git a/sdk-base/src/main/java/com/securenative/models/RiskResult.java b/src/main/java/com/securenative/models/VerifyResult.java similarity index 59% rename from sdk-base/src/main/java/com/securenative/models/RiskResult.java rename to src/main/java/com/securenative/models/VerifyResult.java index 65e70ae..1ee4acc 100644 --- a/sdk-base/src/main/java/com/securenative/models/RiskResult.java +++ b/src/main/java/com/securenative/models/VerifyResult.java @@ -1,35 +1,36 @@ package com.securenative.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.securenative.enums.RiskLevel; @JsonIgnoreProperties(ignoreUnknown = true) -public class RiskResult { - private String riskLevel; - private double score; +public class VerifyResult { + private RiskLevel riskLevel; + private float score; private String[] triggers; - public RiskResult() { + public VerifyResult() { } - public RiskResult(String riskLevel, double score, String[] triggers) { + public VerifyResult(RiskLevel riskLevel, float score, String[] triggers) { this.riskLevel = riskLevel; this.score = score; this.triggers = triggers; } - public String getRiskLevel() { + public RiskLevel getRiskLevel() { return riskLevel; } - public void setRiskLevel(String riskLevel) { + public void setRiskLevel(RiskLevel riskLevel) { this.riskLevel = riskLevel; } - public double getScore() { + public float getScore() { return score; } - public void setScore(double score) { + public void setScore(float score) { this.score = score; } diff --git a/src/main/java/com/securenative/utils/DateUtils.java b/src/main/java/com/securenative/utils/DateUtils.java new file mode 100644 index 0000000..842a8e6 --- /dev/null +++ b/src/main/java/com/securenative/utils/DateUtils.java @@ -0,0 +1,21 @@ +package com.securenative.utils; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +public class DateUtils { + private static final DateTimeFormatter ISO_8601_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + public static String generateTimestamp() { + return ZonedDateTime.now(ZoneOffset.UTC).format(ISO_8601_PATTERN); + } + + public static String toTimestamp(Date date) { + if (date == null) { + date = new Date(); + } + return ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC).format(ISO_8601_PATTERN); + } +} diff --git a/src/main/java/com/securenative/utils/EncryptionUtils.java b/src/main/java/com/securenative/utils/EncryptionUtils.java new file mode 100644 index 0000000..daa4f8d --- /dev/null +++ b/src/main/java/com/securenative/utils/EncryptionUtils.java @@ -0,0 +1,98 @@ +package com.securenative.utils; + +import com.securenative.Logger; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + + +public class EncryptionUtils { + private static final String EMPTY_STRING = ""; + private static final Logger logger = Logger.getLogger(EncryptionUtils.class); + private static final int AES_KEY_SIZE = 32; + private final static char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static byte[] hexToByteArray(String s) { + byte[] retValue = null; + if (s != null && s.length() != 0) { + retValue = new byte[s.length() / 2]; + for (int i = 0; i < retValue.length; i++) { + retValue[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16); + } + } + return retValue; + } + + private static String byteArrayToHex(byte[] byteArray) { + StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2); + for (byte b : byteArray) + for (int j = 1; j >= 0; j--) + hexBuffer.append(HEX[(b >> (j * 4)) & 0xF]); + return hexBuffer.toString(); + } + + private byte[] pad(byte[] buf, int size) { + int bufLen = buf.length; + int padLen = size - bufLen % size; + byte[] padded = new byte[bufLen + padLen]; + padded = Arrays.copyOf(buf, bufLen + padLen); + for (int i = 0; i < padLen; i++) { + padded[bufLen + i] = (byte) padLen; + } + return padded; + } + + private static byte[] addAll(final byte[] array1, byte[] array2) { + byte[] joinedArray = Arrays.copyOf(array1, array1.length + array2.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + public static String encrypt(String text, String key) { + try { + SecureRandom secureRandom = new SecureRandom(); + byte[] ivBytes = new byte[16]; + secureRandom.nextBytes(ivBytes); + AlgorithmParameterSpec IVspec = new IvParameterSpec(ivBytes); + SecretKeySpec skeySpec = new SecretKeySpec(key.substring(0, AES_KEY_SIZE).getBytes(StandardCharsets.UTF_8), "AES"); + byte[] source = text.getBytes(StandardCharsets.UTF_8); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, IVspec); + int mod = source.length % 16; + if (mod != 0) { + text = String.format(text + "%" + (16 - mod) + "s", " "); + } + return byteArrayToHex(cipher.doFinal(addAll(ivBytes, text.getBytes(StandardCharsets.UTF_8)))).trim(); + } catch (Exception ex) { + logger.error("Unable to encrypt, err:", ex.getMessage()); + } + + return EMPTY_STRING; + } + + public static String decrypt(String s, String key) { + logger.info("Starting to decrypt " + s); + if (s == null || s.length() == 0) { + return s; + } + byte[] cipherText = hexToByteArray(s); + try { + SecretKeySpec skeySpec = new SecretKeySpec(key.substring(0, AES_KEY_SIZE).getBytes("UTF-8"), "AES"); + byte[] ivBytes = Arrays.copyOfRange(cipherText, 0, AES_KEY_SIZE / 2); + cipherText = Arrays.copyOfRange(cipherText, AES_KEY_SIZE / 2, cipherText.length); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + AlgorithmParameterSpec IVspec = new IvParameterSpec(ivBytes); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, IVspec); + return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8).trim(); + } catch (Exception ex) { + logger.error("Unable to decrypt", ex.getMessage()); + } + + return EMPTY_STRING; + } +} diff --git a/src/main/java/com/securenative/utils/IPUtils.java b/src/main/java/com/securenative/utils/IPUtils.java new file mode 100644 index 0000000..c02d07a --- /dev/null +++ b/src/main/java/com/securenative/utils/IPUtils.java @@ -0,0 +1,44 @@ +package com.securenative.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IPUtils { + private static final Pattern VALID_IPV4_PATTERN = Pattern.compile("(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])", Pattern.CASE_INSENSITIVE); + private static final Pattern VALID_IPV6_PATTERN = Pattern.compile("([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}", Pattern.CASE_INSENSITIVE); + + public static boolean isIpAddress(String ipAddress) { + Matcher m1 = VALID_IPV4_PATTERN.matcher(ipAddress); + if (m1.matches()) { + return true; + } + Matcher m2 = VALID_IPV6_PATTERN.matcher(ipAddress); + return m2.matches(); + } + + public static boolean isValidPublicIp(String ip) { + InetAddress address; + try { + address = InetAddress.getByName(ip); + } catch (UnknownHostException exception) { + return false; + } + + return !(address.isSiteLocalAddress() || + address.isAnyLocalAddress() || + address.isLinkLocalAddress() || + address.isLoopbackAddress() || + address.isMulticastAddress()); + } + + public static boolean isLoopBack(String ip) { + try { + return InetAddress.getByName(ip).isLoopbackAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/src/main/java/com/securenative/utils/RequestUtils.java b/src/main/java/com/securenative/utils/RequestUtils.java new file mode 100644 index 0000000..52bd3a7 --- /dev/null +++ b/src/main/java/com/securenative/utils/RequestUtils.java @@ -0,0 +1,113 @@ +package com.securenative.utils; + +import com.securenative.config.SecureNativeOptions; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RequestUtils { + public final static String SECURENATIVE_COOKIE = "_sn"; + public final static String SECURENATIVE_HEADER = "x-securenative"; + private final static List ipHeaders = Arrays.asList("x-forwarded-for", "x-client-ip", "x-real-ip", "x-forwarded", "x-cluster-client-ip", "forwarded-for", "forwarded", "via"); + private final static List piiHeaders = Arrays.asList("authorization", "access_token", "apikey", "password", "passwd", "secret", "api_key"); + + public static Map getHeadersFromRequest(HttpServletRequest request, SecureNativeOptions options) { + Map headersMap = new HashMap<>(); + if (options != null && options.getPiiHeaders().size() > 0) { + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); ) { + String headerName = headerNames.nextElement(); + if (!options.getPiiHeaders().contains(headerName.toLowerCase()) && !options.getPiiHeaders().contains(headerName.toUpperCase())) { + String headerValue = request.getHeader(headerName); + headersMap.put(headerName, headerValue); + } + } + } else if (options != null && options.getPiiRegexPattern() != null) { + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); ) { + String headerName = headerNames.nextElement(); + Pattern pattern = Pattern.compile(options.getPiiRegexPattern(), Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(headerName); + if (!matcher.find()) { + String headerValue = request.getHeader(headerName); + headersMap.put(headerName, headerValue); + } + } + } else { + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); ) { + String headerName = headerNames.nextElement(); + if (!piiHeaders.contains(headerName.toLowerCase()) && !piiHeaders.contains(headerName.toUpperCase())) { + String headerValue = request.getHeader(headerName); + headersMap.put(headerName, headerValue); + } + } + } + + return headersMap; + } + + public static String getSecureHeaderFromRequest(Map headers) { + return headers.getOrDefault(SECURENATIVE_HEADER, ""); + } + + public static String getCookieValueFromRequest(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) { + return cookie.getValue(); + } + } + } + return null; + } + + public static String getClientIpFromRequest(HttpServletRequest request, Map headers, SecureNativeOptions options) { + if (options != null && options.getProxyHeaders().size() > 0) { + for (String header : options.getProxyHeaders()) { + if (headers.containsKey(header)) { + String headerValue = headers.get(header); + + Optional ip = Arrays.stream(headerValue.split(",")) + .map(String::trim) + .filter(IPUtils::isIpAddress) + .filter(IPUtils::isValidPublicIp) + .findFirst(); + + if (ip.isPresent()) { + return ip.get(); + } + } + } + } + + Optional bestCandidate = Optional.empty(); + for (String ipHeader : ipHeaders) { + if (!headers.containsKey(ipHeader)) { + continue; + } + String headerValue = headers.get(ipHeader); + + Optional candidateIp = Arrays.stream(headerValue.split(",")) + .map(String::trim) + .filter(IPUtils::isIpAddress) + .filter(IPUtils::isValidPublicIp) + .findFirst(); + + if (candidateIp.isPresent()) { + return candidateIp.get(); + } else if (!bestCandidate.isPresent()) { + bestCandidate = Arrays.stream(headerValue.split(",")) + .map(String::trim) + .filter(IPUtils::isLoopBack) + .findFirst(); + } + } + return bestCandidate.orElseGet(request::getRemoteAddr); + } + + public static String getRemoteIpFromRequest(HttpServletRequest request) { + return request.getRemoteAddr(); + } +} diff --git a/src/main/java/com/securenative/utils/SignatureUtils.java b/src/main/java/com/securenative/utils/SignatureUtils.java new file mode 100644 index 0000000..8f4b59a --- /dev/null +++ b/src/main/java/com/securenative/utils/SignatureUtils.java @@ -0,0 +1,42 @@ +package com.securenative.utils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.util.Formatter; + +import static com.securenative.utils.Utils.timingSafeEqual; + +public class SignatureUtils { + public final static String SIGNATURE_HEADER = "x-securenative"; + private static final String HMAC_SHA512 = "HmacSHA512"; + + private static String toHexString(byte[] bytes) { + Formatter formatter = new Formatter(); + for (byte b : bytes) { + formatter.format("%02x", b); + } + + return formatter.toString(); + } + + private static String buildHmacSignature(String message, String key) { + try { + Mac hasher = Mac.getInstance(HMAC_SHA512); + hasher.init(new SecretKeySpec(key.getBytes(), HMAC_SHA512)); + byte[] hash = hasher.doFinal(message.getBytes()); + return toHexString(hash); + } catch (Exception ignored) { + } + + return ""; + } + + public static boolean isValidSignature(String headerSignature, String payload, String apiKey) { + String signed = buildHmacSignature(payload, apiKey); + if (Utils.isNullOrEmpty(signed) || Utils.isNullOrEmpty(headerSignature)) { + return false; + } + + return timingSafeEqual(headerSignature.getBytes(), signed.getBytes()); + } +} diff --git a/src/main/java/com/securenative/utils/Utils.java b/src/main/java/com/securenative/utils/Utils.java new file mode 100644 index 0000000..b17fbb6 --- /dev/null +++ b/src/main/java/com/securenative/utils/Utils.java @@ -0,0 +1,40 @@ +package com.securenative.utils; + +import com.securenative.Logger; + + +public class Utils { + private static final Logger logger = Logger.getLogger(Utils.class); + + public static boolean timingSafeEqual(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + + int result = 0; + for (int i = 0; i < a.length; i++) { + result |= a[i] ^ b[i]; + } + return result == 0; + } + + public static Boolean isNullOrEmpty(String str) { + return str == null || str.length() == 0; + } + + public static Integer parseIntegerOrDefault(String str, Integer defaultValue) { + try { + return Integer.valueOf(str); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + public static Boolean parseBooleanOrDefault(String str, Boolean defaultValue) { + try { + return Boolean.valueOf(str); + } catch (Exception ex) { + return defaultValue; + } + } +} diff --git a/src/main/java/com/securenative/utils/VersionUtils.java b/src/main/java/com/securenative/utils/VersionUtils.java new file mode 100644 index 0000000..9eb4a7d --- /dev/null +++ b/src/main/java/com/securenative/utils/VersionUtils.java @@ -0,0 +1,52 @@ +package com.securenative.utils; + +import com.securenative.ResourceStream; +import com.securenative.ResourceStreamImpl; + +import java.io.InputStream; +import java.util.Properties; + +public class VersionUtils { + private static ResourceStream resourceStream = new ResourceStreamImpl(); + + public static void setResourceStream(ResourceStream resourceStream) { + VersionUtils.resourceStream = resourceStream; + } + + public static synchronized String getVersion() { + String version = null; + + // try to load from maven properties first + try { + Properties p = new Properties(); + InputStream is = resourceStream.getInputStream("/META-INF/maven/com.securenative.java/securenative-java/pom.properties"); + if (is != null) { + p.load(is); + version = p.getProperty("version", ""); + } + } catch (Exception e) { + // ignore + } + + // fallback to using Java API + if (version == null) { + Package aPackage = VersionUtils.class.getPackage(); + if (aPackage != null) { + version = aPackage.getImplementationVersion(); + if (version == null) { + version = aPackage.getSpecificationVersion(); + } + } + } + + if (version == null) { + // we could not compute the version so use a blank + version = "unknown"; + } + + return version; + } +} + + + diff --git a/src/test/java/com/securenative/ApiManagerImplTest.java b/src/test/java/com/securenative/ApiManagerImplTest.java new file mode 100644 index 0000000..e145bac --- /dev/null +++ b/src/test/java/com/securenative/ApiManagerImplTest.java @@ -0,0 +1,251 @@ +package com.securenative; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.securenative.config.ConfigurationManager; +import com.securenative.context.SecureNativeContext; +import com.securenative.enums.EventTypes; +import com.securenative.enums.RiskLevel; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.http.HTTPServerMock; +import com.securenative.models.EventOptions; +import com.securenative.models.VerifyResult; +import okhttp3.mockwebserver.RecordedRequest; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.util.Date; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ApiManagerImplTest extends HTTPServerMock { + private final EventOptions eventOptions; + + public ApiManagerImplTest() throws SecureNativeInvalidOptionsException { + // init default event for tests + SecureNativeContext context = SecureNative.contextBuilder() + .withIp("127.0.0.1") + .withClientToken("SECURED_CLIENT_TOKEN") + .withHeaders(Maps.defaultBuilder() + .put("user-agent", "Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405") + .build()) + .build(); + + eventOptions = EventOptionsBuilder.builder(EventTypes.LOG_IN) + .userId("USER_ID") + .userTraits("USER_NAME", "USER_EMAIL") + .context(context) + .properties(Maps.builder() + .put("prop1", "CUSTOM_PARAM_VALUE") + .put("prop2", true) + .put("prop3", 3) + .build()) + .timestamp(new Date()) + .build(); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should call track event") + public void ShouldCallTrackEventTest() throws SecureNativeSDKException, InterruptedException, JSONException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(200); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + try { + // track async event + apiManager.track(eventOptions); + + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String body = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + String expected = "{\"eventType\":\"sn.user.login\",\"userId\":\"USER_ID\",\"userTraits\":{\"name\":\"USER_NAME\",\"email\":\"USER_EMAIL\",\"createdAt\":null},\"request\":{\"cid\":null,\"vid\":null,\"fp\":null,\"ip\":\"127.0.0.1\",\"remoteIp\":null,\"headers\":{\"user-agent\":\"Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405\"},\"url\":null,\"method\":null},\"properties\":{\"prop2\":true,\"prop1\":\"CUSTOM_PARAM_VALUE\",\"prop3\":3}}"; + assertThat(body).isNotNull(); + JSONAssert.assertEquals(expected, body, false); + assertThat(new JSONObject(body).has("rid")).isTrue(); + assertThat(new JSONObject(body).has("timestamp")).isTrue(); + } catch (SecureNativeInvalidOptionsException ignored) { + } finally { + eventManager.stopEventsPersist(); + } + } + + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should throw when sending more than 10 custom properties to track event") + public void ShouldThrowWhenSendingMoreThan10CustomPropertiesToTrackEventTest() throws SecureNativeSDKException, SecureNativeInvalidOptionsException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(200); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + Map props = IntStream.range(0, 11).boxed().collect(Collectors.toMap(i -> String.format("prop%d", i), i -> String.format("val%d", i))); + + try { + assertThrows(SecureNativeInvalidOptionsException.class, () -> { + // track async event + apiManager.track(EventOptionsBuilder.builder(EventTypes.LOG_IN) + .properties(props) + .build()); + }); + } finally { + eventManager.stopEventsPersist(); + } + } + + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should not call track event when automatic persistence disabled") + public void ShouldNotCallTrackEventWhenAutomaticPersistenceDisabledTest() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(500); + eventManager = new SecureNativeEventManager(client, options); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + // track async event + try { + apiManager.track(eventOptions); + } catch (SecureNativeInvalidOptionsException ignored) { + } + + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + + assertThat(lastRequest).isNull(); + assertThat(server.getRequestCount()).isEqualTo(0); + } + + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should not retry unauthorized track event call") + public void ShouldNotRetryUnauthorizedTrackEventCallTest() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(401); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + // track async event + try { + apiManager.track(eventOptions); + } catch (SecureNativeInvalidOptionsException ignored) { + } + + try { + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + + assertThat(lastRequest).isNotNull(); + + // ensure event to be again after backoff + Thread.sleep(10 * options.getInterval()); + + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + eventManager.stopEventsPersist(); + } + } + + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should call verify event") + public void ShouldCallVerifyEventTest() throws SecureNativeSDKException, JsonProcessingException, InterruptedException, JSONException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + VerifyResult verifyResult = new VerifyResult(RiskLevel.LOW, 0, new String[0]); + String body = new ObjectMapper().writeValueAsString(verifyResult); + + client = sandbox().mock(200, body); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + + // call verify event + VerifyResult result = null; + try { + result = apiManager.verify(eventOptions); + } catch (SecureNativeInvalidOptionsException ignored) { + } + + assert result != null; + assertThat(result.getRiskLevel()).isEqualTo(verifyResult.getRiskLevel()); + assertThat(result.getScore()).isEqualTo(verifyResult.getScore()); + assertThat(result.getTriggers().length).isEqualTo(verifyResult.getTriggers().length); + + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String lastRequestBody = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + String expected = "{\"eventType\":\"sn.user.login\",\"userId\":\"USER_ID\",\"userTraits\":{\"name\":\"USER_NAME\",\"email\":\"USER_EMAIL\",\"createdAt\":null},\"request\":{\"cid\":null,\"vid\":null,\"fp\":null,\"ip\":\"127.0.0.1\",\"remoteIp\":null,\"headers\":{\"user-agent\":\"Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405\"},\"url\":null,\"method\":null},\"properties\":{\"prop2\":true,\"prop1\":\"CUSTOM_PARAM_VALUE\",\"prop3\":3}}"; + assertThat(lastRequestBody).isNotNull(); + JSONAssert.assertEquals(expected, lastRequestBody, false); + assertThat(new JSONObject(lastRequestBody).has("rid")).isTrue(); + assertThat(new JSONObject(lastRequestBody).has("timestamp")).isTrue(); + } + + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should fail verify event call when unauthorized") + public void ShouldFailVerifyEventCallWhenUnauthorizedTest() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + client = sandbox().mock(401); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + ApiManager apiManager = new ApiManagerImpl(eventManager, options); + + // call verify event + VerifyResult verifyResult = null; + try { + verifyResult = apiManager.verify(eventOptions); + } catch (SecureNativeInvalidOptionsException ignored) { + } + + assert verifyResult != null; + assertThat(verifyResult.getRiskLevel()).isEqualTo(RiskLevel.LOW); + assertThat(verifyResult.getScore()).isEqualTo(0); + assertThat(verifyResult.getTriggers().length).isEqualTo(0); + + assertThat(verifyResult).isNotNull(); + + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String lastRequestBody = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + + assertThat(lastRequestBody).isNotNull(); + } +} diff --git a/src/test/java/com/securenative/EventManagerTest.java b/src/test/java/com/securenative/EventManagerTest.java new file mode 100644 index 0000000..0b624f1 --- /dev/null +++ b/src/test/java/com/securenative/EventManagerTest.java @@ -0,0 +1,293 @@ +package com.securenative; + +import com.fasterxml.jackson.databind.JsonNode; +import com.securenative.config.ConfigurationManager; +import com.securenative.exceptions.SecureNativeParseException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.http.HTTPServerMock; +import com.securenative.models.Event; +import com.securenative.models.SampleEvent; +import okhttp3.mockwebserver.RecordedRequest; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EventManagerTest extends HTTPServerMock { + private final Event event = new SampleEvent(); + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should successfully send async event with status code 200") + public void SendAsyncEventWithStatusCode200Test() throws SecureNativeSDKException, InterruptedException, JSONException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(200); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + + eventManager.sendAsync(event, "some-path/to-api", true); + + try { + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String body = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + String expected = "{\"eventType\":\"custom-event\"}"; + + JSONAssert.assertEquals(expected, body, false); + assertThat(new JSONObject(body).has("timestamp")).isTrue(); + } finally { + eventManager.stopEventsPersist(); + } + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should handle async event invalid json response with status 200") + public void ShouldHandleInvalidJsonResponseWithStatus200Test() throws InterruptedException, JSONException, SecureNativeSDKException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(200, "bla bla"); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + + try { + // track async event + eventManager.sendAsync(event, "some-path/to-api", true); + + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String body = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + + assertThat(body).isNotNull(); + //timestamp + assertThat(new JSONObject(body).has("timestamp")).isTrue(); + //event type + assertThat(new JSONObject(body).get("eventType")).isEqualTo(event.getEventType()); + } finally { + eventManager.stopEventsPersist(); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should not retry sending async event when status code 200") + public void ShouldNotRetrySendingAsyncEventWhenStatusCode200Test() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + + client = sandbox().mock(200); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + + try { + // track async event + eventManager.sendAsync(event, "some-path/to-api", true); + + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + + // first attempt should be called + assertThat(lastRequest).isNotNull(); + + // let's give option to send again + Thread.sleep(2 * options.getInterval()); + + // should be called only once + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + eventManager.stopEventsPersist(); + } + } + + @Test + @Timeout(value = 4000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should not retry sending async event when status code 401") + public void ShouldNotRetrySendingAsyncEventWhenStatusCode401Test() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + + client = sandbox().mock(401); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + + try { + // track async event + eventManager.sendAsync(event, "some-path/to-api", true); + + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + + + assertThat(lastRequest).isNotNull(); + + // let give client time to retry + Thread.sleep(10 * options.getInterval()); + + // should be called only once + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + eventManager.stopEventsPersist(); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should retry sending async event when status code 500") + public void ShouldRetrySendingAsyncEventWhenStatusCode500Test() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY") + .withAutoSend(true) + .withInterval(10); + + client = sandbox().mock(500); + eventManager = new SecureNativeEventManager(client, options); + eventManager.startEventsPersist(); + + try { + // track async event + eventManager.sendAsync(event, "some-path/to-api", true); + + // ensure event to be sent + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + + assertThat(lastRequest).isNotNull(); + + //set mock to successful attempt + mock(200); + + Thread.sleep(20 * options.getInterval()); + assertThat(server.getRequestCount()).isEqualTo(2); + } finally { + eventManager.stopEventsPersist(); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should successfully send sync event with status code 200") + public void ShouldSuccessfullySendSyncEventWithStatusCode200Test() throws SecureNativeSDKException, JSONException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + String resBody = "{ \"data\": true }"; + client = sandbox().mock(200, resBody); + eventManager = new SecureNativeEventManager(client, options); + + // track async event + JsonNode data = null; + try { + data = eventManager.sendSync(JsonNode.class, event, "some-path/to-api"); + } catch (Exception ignored) { + } + + JSONAssert.assertEquals(resBody, data.toString(), false); + + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + String body = lastRequest != null ? lastRequest.getBody().readUtf8() : null; + String expected = "{\"eventType\":\"custom-event\"}"; + + JSONAssert.assertEquals(expected, body, false); + assertThat(new JSONObject(body).has("timestamp")).isTrue(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should send sync event and handle invalid json response") + public void ShouldSendSyncEventAndHandleInvalidJsonResponseTest() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + client = sandbox().mock(200, "bla bla"); + eventManager = new SecureNativeEventManager(client, options); + + + // track async event + String resp = null; + try { + resp = eventManager.sendSync(String.class, event, "some-path/to-api"); + } catch (Exception ignored) { + } + + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + //invalid json response will turn into null + assertThat(resp).isNull(); + assertThat(lastRequest).isNotNull(); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should send sync event and handle invalid request url") + public void ShouldSendSyncEventAndHandleInvalidRequestUrlTest() throws SecureNativeSDKException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + client = sandbox().mock(500); + eventManager = new SecureNativeEventManager(client, options); + + try { + Object obj = eventManager.sendSync(Object.class, event, "path what"); + assertThat(obj).isNull(); + } catch (Exception ignored) { + + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should send sync event and fail when status code 401") + public void ShouldSendSyncEventAndFailWhenStatusCode401Test() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + + client = sandbox().mock(401); + eventManager = new SecureNativeEventManager(client, options); + + try { + // track async event + eventManager.sendSync(Object.class, event, "some-path/to-api"); + } catch (SecureNativeParseException | IOException ex) { + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + assertThat(lastRequest).isNotNull(); + assertThat(ex.getMessage()).contains("401"); + } + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should send sync event and fail when status code 500") + public void ShouldSendSyncEventAndFailWhenStatusCode500Test() throws SecureNativeSDKException, InterruptedException { + configBuilder = ConfigurationManager.configBuilder() + .withApiKey("YOUR_API_KEY"); + + client = sandbox().mock(500); + eventManager = new SecureNativeEventManager(client, options); + + try { + // track async event + eventManager.sendSync(Object.class, event, "some-path/to-api"); + } catch (Exception ex) { + RecordedRequest lastRequest = server.takeRequest(10 * options.getInterval(), TimeUnit.MILLISECONDS); + assertThat(lastRequest).isNotNull(); + assertThat(ex.getMessage()).contains("500"); + } + } +} diff --git a/src/test/java/com/securenative/SecureNativeTest.java b/src/test/java/com/securenative/SecureNativeTest.java new file mode 100644 index 0000000..f32b009 --- /dev/null +++ b/src/test/java/com/securenative/SecureNativeTest.java @@ -0,0 +1,135 @@ +package com.securenative; + +import com.securenative.config.SecureNativeOptions; +import com.securenative.enums.FailoverStrategy; +import com.securenative.exceptions.SecureNativeConfigException; +import com.securenative.exceptions.SecureNativeSDKException; +import com.securenative.exceptions.SecureNativeSDKIllegalStateException; +import org.junit.jupiter.api.*; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class SecureNativeTest { + @AfterEach + public void cleanUp() throws NoSuchFieldException, IllegalAccessException { + Field instance = SecureNative.class.getDeclaredField("secureNative"); + instance.setAccessible(true); + instance.set(null, null); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(1) + @DisplayName("Should init SDK with all public methods defined") + public void initSDKWithPublicMethodsDefinedTest() { + assertThat(SecureNative.class).hasDeclaredMethods("track", "verify"); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(2) + @DisplayName("Should throw when getting SDK instance without init") + public void getSDKInstanceWithoutInitThrowsTest() { + assertThrows(SecureNativeSDKIllegalStateException.class, SecureNative::getInstance); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @SetSystemProperty(key = "SECURENATIVE_CONFIG_FILE", value = "FILE_THAT_MISSING") + @Order(3) + @DisplayName("Should throw when try init sdk without api key") + public void initSDKWithoutApiKeyShouldThrowTest() throws SecureNativeConfigException, SecureNativeSDKException { + assertThrows(SecureNativeSDKException.class, () -> { + SecureNative secureNative = SecureNative.init(); + }); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(4) + @DisplayName("Should throw when try init sdk with empty api key") + public void initSDKWithEmptyApiKeyShouldThrowTest() { + assertThrows(SecureNativeConfigException.class, () -> { + SecureNative.init(""); + }); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(5) + @DisplayName("Should init SDK with API key and default options") + public void initSDKWithApiKeyAndDefaultsTest() throws SecureNativeConfigException, SecureNativeSDKException { + final String apiKey = "API_KEY"; + SecureNative secureNative = SecureNative.init(apiKey); + SecureNativeOptions options = secureNative.getOptions(); + + assertThat(options.getApiKey()).isEqualTo(apiKey); + assertThat(options.getApiUrl()).isEqualTo("https://api.securenative.com/collector/api/v1"); + assertThat(options.getInterval()).isEqualTo(1000); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getMaxEvents()).isEqualTo(1000); + assertThat(options.getAutoSend()).isEqualTo(true); + assertThat(options.getAutoSend()).isEqualTo(true); + assertThat(options.getDisabled()).isEqualTo(false); + assertThat(options.getLogLevel()).isEqualTo("fatal"); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_OPEN); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(6) + @DisplayName("Should throw exception when SDK initialized twice") + public void initSDKTwiceWillThrowTest() { + assertThrows(SecureNativeSDKException.class, () -> { + SecureNative.init(); + SecureNative.init(); + }); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(7) + @DisplayName("Initialized SDK instance should match singleton") + public void initSDKWithApiKeyAndGetInstanceShouldMatchTest() throws SecureNativeConfigException, SecureNativeSDKException { + final String apiKey = "API_KEY"; + SecureNative secureNative = SecureNative.init(apiKey); + + assertThat(secureNative).isEqualTo(SecureNative.getInstance()); + assertThat(secureNative).isEqualTo(SecureNative.getInstance()); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(8) + @DisplayName("Should init SDK with builder correctly") + public void initSDKWithBuilderTest() throws SecureNativeConfigException, SecureNativeSDKException { + SecureNative secureNative = SecureNative.init(SecureNative.configBuilder() + .withApiKey("API_KEY") + .withMaxEvents(10) + .withLogLevel("error") + .build()); + + SecureNativeOptions options = secureNative.getOptions(); + + assertThat(options).extracting("apiKey", "maxEvents", "logLevel") + .containsExactly("API_KEY", 10, "error"); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @Order(9) + @DisplayName("Should init SDK with property file correctly") + public void initSDKAndLoadFromPropertiesFileTest() throws SecureNativeConfigException, SecureNativeSDKException { + SecureNative secureNative = SecureNative.init(); + SecureNativeOptions options = secureNative.getOptions(); + assertThat(options).extracting("apiKey", "timeout") + .containsExactly("SOME_API_KEY", 2000); + } +} diff --git a/src/test/java/com/securenative/config/ConfigurationManagerTest.java b/src/test/java/com/securenative/config/ConfigurationManagerTest.java new file mode 100644 index 0000000..0997128 --- /dev/null +++ b/src/test/java/com/securenative/config/ConfigurationManagerTest.java @@ -0,0 +1,305 @@ +package com.securenative.config; + +import com.securenative.ResourceStreamImpl; +import com.securenative.enums.FailoverStrategy; +import com.securenative.exceptions.SecureNativeConfigException; +import org.junit.jupiter.api.*; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ConfigurationManagerTest { + @SuppressWarnings({"unchecked"}) + private static void setEnv(String name, String val) throws ReflectiveOperationException { + Map env = System.getenv(); + Field field = env.getClass().getDeclaredField("m"); + field.setAccessible(true); + ((Map) field.get(env)).put(name, val); + } + + @Test + @Order(1) + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should parse config file correctly") + public void ParseConfigFileCorrectlyTest() throws SecureNativeConfigException { + String config = String.join(System.getProperty("line.separator"), + "SECURENATIVE_API_KEY=SOME_API_KEY", + "SECURENATIVE_APP_NAME=SOME_APP_NAME", + "SECURENATIVE_API_URL=SOME_API_URL", + "SECURENATIVE_INTERVAL=1000", + "SECURENATIVE_HEARTBEAT_INTERVAL=5000", + "SECURENATIVE_MAX_EVENTS=100", + "SECURENATIVE_TIMEOUT=1500", + "SECURENATIVE_AUTO_SEND=true", + "SECURENATIVE_DISABLE=false", + "SECURENATIVE_LOG_LEVEL=fatal", + "SECURENATIVE_FAILOVER_STRATEGY=fail-closed", + "SECURENATIVE_PROXY_HEADERS=CF-Connecting-IP,Some-Random-Ip", + "SECURENATIVE_PII_HEADERS=authentication,apiKey", + "SECURENATIVE_PII_REGEX_PATTERN=/http_auth_/i"); + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options).isNotNull(); + assertThat(options.getApiKey()).isEqualTo("SOME_API_KEY"); + assertThat(options.getApiUrl()).isEqualTo("SOME_API_URL"); + assertThat(options.getAutoSend()).isEqualTo(true); + assertThat(options.getDisabled()).isEqualTo(false); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_CLOSED); + assertThat(options.getInterval()).isEqualTo(1000); + assertThat(options.getLogLevel()).isEqualTo("fatal"); + assertThat(options.getMaxEvents()).isEqualTo(100); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getProxyHeaders().size() == 0); + assertThat(options.getPiiHeaders().size() == 0); + assertThat(options.getPiiRegexPattern()).isEqualTo("/http_auth_/i"); + + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + @Test + @Order(2) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should ignore unknown config file entries") + public void IgnoreUnknownConfigInPropertiesFileTest() throws SecureNativeConfigException { + String config = String.join(System.getProperty("line.separator"), + "SECURENATIVE_UNKNOWN_KEY=SOME_UNKNOWN_KEY", + "SECURENATIVE_TIMEOUT=7500"); + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options).isNotNull(); + assertThat(options.getTimeout()).isEqualTo(7500); + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + + @Test + @Order(3) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should not throw when parsing invalid config file") + public void handleInvalidConfigFileTest() throws SecureNativeConfigException { + String config = "{bla bla bla}"; + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options).isNotNull(); + + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + @Test + @Order(4) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should ignore invalid config file entries") + public void IgnoreInvalidConfigFileEntriesTest() throws SecureNativeConfigException { + String config = String.join(System.getProperty("line.separator"), + "SECURENATIVE_API_KEY=1", + "SECURENATIVE_API_URL=3", + "SECURENATIVE_TIMEOUT=bad timeout", + "SECURENATIVE_FAILOVER_STRATEGY=fail-what"); + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options).isNotNull(); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_OPEN); + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + @Test + @Order(5) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should get default config when config file and env variables are missing") + public void loadDefaultConfigTest() throws SecureNativeConfigException { + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(null); + + //set resource stream + ConfigurationManager.setResourceStream(resourceStream); + + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options.getApiKey()).isNull(); + assertThat(options.getApiUrl()).isEqualTo("https://api.securenative.com/collector/api/v1"); + assertThat(options.getInterval()).isEqualTo(1000); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getMaxEvents()).isEqualTo(1000); + assertThat(options.getAutoSend()).isEqualTo(true); + assertThat(options.getAutoSend()).isEqualTo(true); + assertThat(options.getDisabled()).isEqualTo(false); + assertThat(options.getLogLevel()).isEqualTo("fatal"); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_OPEN); + assertThat(options.getProxyHeaders().size() == 0); + assertThat(options.getPiiHeaders().size() == 0); + assertThat(options.getPiiRegexPattern()).isEqualTo(null); + + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + @Test + @Order(6) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should get config via env variables") + public void getConfigFromEnvVariablesTest() throws SecureNativeConfigException, ReflectiveOperationException { + setEnv("SECURENATIVE_API_KEY", "SOME_ENV_API_KEY"); + setEnv("SECURENATIVE_API_URL", "SOME_API_URL"); + setEnv("SECURENATIVE_INTERVAL", "6000"); + setEnv("SECURENATIVE_MAX_EVENTS", "700"); + setEnv("SECURENATIVE_TIMEOUT", "1700"); + setEnv("SECURENATIVE_AUTO_SEND", "false"); + setEnv("SECURENATIVE_DISABLE", "true"); + setEnv("SECURENATIVE_LOG_LEVEL", "fatal"); + setEnv("SECURENATIVE_FAILOVER_STRATEGY", "fail-closed"); + setEnv("SECURENATIVE_PROXY_HEADERS", "CF-Connecting-IP,Some-Random-Ip"); + + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options.getApiKey()).isEqualTo("SOME_API_KEY"); + assertThat(options.getApiUrl()).isEqualTo("SOME_API_URL"); + assertThat(options.getInterval()).isEqualTo(6000); + assertThat(options.getTimeout()).isEqualTo(2000); + assertThat(options.getMaxEvents()).isEqualTo(700); + assertThat(options.getAutoSend()).isEqualTo(false); + assertThat(options.getDisabled()).isEqualTo(true); + assertThat(options.getLogLevel()).isEqualTo("fatal"); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_CLOSED); + assertThat(options.getProxyHeaders().size() == 2); + + setEnv("SECURENATIVE_API_KEY", ""); + setEnv("SECURENATIVE_API_URL", ""); + setEnv("SECURENATIVE_INTERVAL", ""); + setEnv("SECURENATIVE_MAX_EVENTS", ""); + setEnv("SECURENATIVE_TIMEOUT", ""); + setEnv("SECURENATIVE_AUTO_SEND", ""); + setEnv("SECURENATIVE_DISABLE", ""); + setEnv("SECURENATIVE_LOG_LEVEL", ""); + setEnv("SECURENATIVE_FAILOVER_STRATEGY", ""); + setEnv("SECURENATIVE_PROXY_HEADERS", ""); + } + + @Test + @Order(7) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should overwrite env variables with vales from config file") + public void overwriteEnvVariablesWithConfigFileTest() throws SecureNativeConfigException, ReflectiveOperationException { + String config = String.join(System.getProperty("line.separator"), + "SECURENATIVE_API_KEY=API_KEY_FROM_FILE", + "SECURENATIVE_API_URL=API_URL_FROM_FILE", + "SECURENATIVE_INTERVAL=1000", + "SECURENATIVE_MAX_EVENTS=100", + "SECURENATIVE_TIMEOUT=1500", + "SECURENATIVE_AUTO_SEND=false", + "SECURENATIVE_DISABLE=false", + "SECURENATIVE_LOG_LEVEL=fatal", + "SECURENATIVE_FAILOVER_STRATEGY=fail-closed", + "SECURENATIVE_PROXY_HEADERS=CF-Connecting-IP,Some-Random-Ip"); + + setEnv("SECURENATIVE_API_KEY", "API_KEY_FROM_ENV"); + setEnv("SECURENATIVE_API_URL", "API_URL_ENV"); + setEnv("SECURENATIVE_INTERVAL", "2000"); + setEnv("SECURENATIVE_MAX_EVENTS", "200"); + setEnv("SECURENATIVE_TIMEOUT", "3000"); + setEnv("SECURENATIVE_AUTO_SEND", "true"); + setEnv("SECURENATIVE_DISABLE", "true"); + setEnv("SECURENATIVE_LOG_LEVEL", "error"); + setEnv("SECURENATIVE_FAILOVER_STRATEGY", "fail-open"); + setEnv("SECURENATIVE_PROXY_HEADERS", "CF-Connecting-IP,Some-Random-Ip"); + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + ArrayList proxyHeaders = new ArrayList<>(); + proxyHeaders.add("CF-Connecting-IP"); + proxyHeaders.add("Some-Random-Ip"); + + assertThat(options).isNotNull(); + assertThat(options.getApiKey()).isEqualTo("API_KEY_FROM_FILE"); + assertThat(options.getApiUrl()).isEqualTo("API_URL_FROM_FILE"); + assertThat(options.getInterval()).isEqualTo(1000); + assertThat(options.getTimeout()).isEqualTo(1500); + assertThat(options.getMaxEvents()).isEqualTo(100); + assertThat(options.getAutoSend()).isEqualTo(false); + assertThat(options.getDisabled()).isEqualTo(false); + assertThat(options.getLogLevel()).isEqualTo("fatal"); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_CLOSED); + assertThat(options.getProxyHeaders()).isEqualTo(proxyHeaders); + + setEnv("SECURENATIVE_API_KEY", ""); + setEnv("SECURENATIVE_API_URL", ""); + setEnv("SECURENATIVE_INTERVAL", ""); + setEnv("SECURENATIVE_MAX_EVENTS", ""); + setEnv("SECURENATIVE_TIMEOUT", ""); + setEnv("SECURENATIVE_AUTO_SEND", ""); + setEnv("SECURENATIVE_DISABLE", ""); + setEnv("SECURENATIVE_LOG_LEVEL", ""); + setEnv("SECURENATIVE_FAILOVER_STRATEGY", ""); + setEnv("SECURENATIVE_PROXY_HEADERS", ""); + + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } + + @Test + @Order(8) + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should set defaults for invalid enum properties") + public void defaultValuesForInvalidEnumConfigPropsTest() throws SecureNativeConfigException { + String config = String.join(System.getProperty("line.separator"), + "SECURENATIVE_FAILOVER_STRATEGY=fail-something"); + + InputStream inputStream = new ByteArrayInputStream(config.getBytes()); + + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("securenative.properties")).thenReturn(inputStream); + + ConfigurationManager.setResourceStream(resourceStream); + SecureNativeOptions options = ConfigurationManager.loadConfig(); + + assertThat(options).isNotNull(); + assertThat(options.getFailoverStrategy()).isEqualTo(FailoverStrategy.FAIL_OPEN); + + // restore resource stream + ConfigurationManager.setResourceStream(new ResourceStreamImpl()); + } +} diff --git a/src/test/java/com/securenative/context/SecureNativeContextBuilderTest.java b/src/test/java/com/securenative/context/SecureNativeContextBuilderTest.java new file mode 100644 index 0000000..6b20635 --- /dev/null +++ b/src/test/java/com/securenative/context/SecureNativeContextBuilderTest.java @@ -0,0 +1,117 @@ +package com.securenative.context; + +import com.securenative.Maps; +import com.securenative.SecureNative; +import com.securenative.config.SecureNativeConfigurationBuilder; +import com.securenative.config.SecureNativeOptions; +import com.securenative.exceptions.SecureNativeSDKException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.springframework.mock.web.MockHttpServletRequest; + +import javax.servlet.http.Cookie; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class SecureNativeContextBuilderTest { + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Create context from http servlet request test") + public void createContextFromHttpServletRequestTest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.setRequestURI("/login"); + request.setQueryString("param1=value1¶m2=value2"); + request.setMethod("Post"); + request.setRemoteAddr("51.68.201.122"); + request.addHeader("x-securenative", "71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a"); + + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + try { + SecureNative secureNative = SecureNative.init(options); + SecureNativeContext context = secureNative.fromHttpServletRequest(request).build(); + + assertThat(context.getClientToken()).isEqualTo("71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a"); + assertThat(context.getIp()).isEqualTo("51.68.201.122"); + assertThat(context.getMethod()).isEqualTo("Post"); + assertThat(context.getUrl()).isEqualTo("/login"); + assertThat(context.getRemoteIp()).isEqualTo("51.68.201.122"); + assertThat(context.getHeaders()).isEqualTo(Maps.defaultBuilder().put("x-securenative", "71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a").build()); + assertThat(context.getBody()).isNull(); + } catch (SecureNativeSDKException ignored) {} + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Create context from http servlet request with cookie test") + public void createContextFromHttpServletRequestWithCookieTest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.setRequestURI("/login"); + request.setQueryString("param1=value1¶m2=value2"); + request.setMethod("Post"); + request.setRemoteAddr("51.68.201.122"); + request.setCookies(new Cookie("_sn", "71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a")); + + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + try { + SecureNative secureNative = SecureNative.init(options); + SecureNativeContext context = secureNative.fromHttpServletRequest(request).build(); + + assertThat(context.getClientToken()).isEqualTo("71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a"); + assertThat(context.getIp()).isEqualTo("51.68.201.122"); + assertThat(context.getMethod()).isEqualTo("Post"); + assertThat(context.getUrl()).isEqualTo("/login"); + assertThat(context.getRemoteIp()).isEqualTo("51.68.201.122"); + assertThat(context.getHeaders()).isEqualTo(Maps.defaultBuilder().put("Cookie", "_sn=71532c1fad2c7f56118f7969e401f3cf080239140d208e7934e6a530818c37e544a0c2330a487bcc6fe4f662a57f265a3ed9f37871e80529128a5e4f2ca02db0fb975ded401398f698f19bb0cafd68a239c6caff99f6f105286ab695eaf3477365bdef524f5d70d9be1d1d474506b433aed05d7ed9a435eeca357de57817b37c638b6bb417ffb101eaf856987615a77a").build()); + assertThat(context.getBody()).isNull(); + } catch (SecureNativeSDKException ignored) {} + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Create default context builder") + public void createDefaultContextBuilderTest() { + SecureNativeContext context = SecureNativeContextBuilder.defaultContextBuilder() + .build(); + + assertThat(context.getClientToken()).isNull(); + assertThat(context.getIp()).isNull(); + assertThat(context.getMethod()).isNull(); + assertThat(context.getUrl()).isNull(); + assertThat(context.getRemoteIp()).isNull(); + assertThat(context.getHeaders()).isNull(); + assertThat(context.getBody()).isNull(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Create custom context with ContextBuilder test") + public void createCustomContextWithContextBuilderTest() { + SecureNativeContext context = SecureNativeContextBuilder + .defaultContextBuilder() + .withUrl("/some-url") + .withClientToken("SECRET_TOKEN") + .withIp("10.0.0.0") + .withBody("{ \"name\": \"YOUR_NAME\" }") + .withMethod("Get") + .withRemoteIp("10.0.0.1") + .withHeaders(Maps.defaultBuilder() + .put("header1", "value1") + .build()) + .build(); + assertThat(context.getUrl()).isEqualTo("/some-url"); + assertThat(context.getClientToken()).isEqualTo("SECRET_TOKEN"); + assertThat(context.getIp()).isEqualTo("10.0.0.0"); + assertThat(context.getBody()).isEqualTo("{ \"name\": \"YOUR_NAME\" }"); + assertThat(context.getMethod()).isEqualTo("Get"); + assertThat(context.getRemoteIp()).isEqualTo("10.0.0.1"); + assertThat(context.getHeaders()).isEqualTo(Maps.defaultBuilder() + .put("header1", "value1") + .build()); + } +} + diff --git a/src/test/java/com/securenative/http/HTTPServerMock.java b/src/test/java/com/securenative/http/HTTPServerMock.java new file mode 100644 index 0000000..a74ff10 --- /dev/null +++ b/src/test/java/com/securenative/http/HTTPServerMock.java @@ -0,0 +1,63 @@ +package com.securenative.http; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.securenative.EventManager; +import com.securenative.config.SecureNativeConfigurationBuilder; +import com.securenative.config.SecureNativeOptions; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; + +import java.io.IOException; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class HTTPServerMock { + private HttpUrl serverUrl; + protected SecureNativeConfigurationBuilder configBuilder; + protected MockWebServer server; + protected SecureNativeOptions options; + protected HttpClient client; + protected EventManager eventManager; + protected final ObjectMapper mapper = new ObjectMapper(); + + @BeforeEach + protected void start() throws IOException { + server = new MockWebServer(); + server.start(); + serverUrl = server.url("/api"); + } + + @AfterEach + protected void shutdown() throws Exception { + server.shutdown(); + } + + public HttpClient mock(int code){ + MockResponse mockResponse = new MockResponse(); + mockResponse.setResponseCode(code); + server.enqueue(mockResponse); + + return this.client; + } + + public HttpClient mock(int code, String body){ + MockResponse mockResponse = new MockResponse(); + mockResponse.setResponseCode(code); + mockResponse.setBody(body); + server.enqueue(mockResponse); + + return this.client; + } + + protected HTTPServerMock sandbox(){ + this.options = configBuilder.withApiUrl(serverUrl.toString()) + .build(); + + this.client = new SecureNativeHTTPClient(options); + + return this; + } +} diff --git a/src/test/java/com/securenative/http/SecureNativeHTTPClientTest.java b/src/test/java/com/securenative/http/SecureNativeHTTPClientTest.java new file mode 100644 index 0000000..a5662f5 --- /dev/null +++ b/src/test/java/com/securenative/http/SecureNativeHTTPClientTest.java @@ -0,0 +1,28 @@ +package com.securenative.http; + +import com.securenative.config.ConfigurationManager; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SecureNativeHTTPClientTest extends HTTPServerMock { + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should make simple http post call") + public void shouldMakeSimplePostCallTest() throws IOException { + configBuilder = ConfigurationManager.configBuilder().withApiKey("YOUR_API_KEY"); + client = sandbox().mock(200, "SOME_BODY"); + String payload = "{\"event\":\"SOME_EVENT_NAME\"}"; + + HttpResponse response = client.post("track", payload); + + assertThat(response.isOk()).isEqualTo(true); + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.getBody()).isEqualTo("SOME_BODY"); + } +} diff --git a/src/test/java/com/securenative/models/SDKEventTest.java b/src/test/java/com/securenative/models/SDKEventTest.java new file mode 100644 index 0000000..c26213f --- /dev/null +++ b/src/test/java/com/securenative/models/SDKEventTest.java @@ -0,0 +1,47 @@ +package com.securenative.models; + +import com.securenative.config.SecureNativeConfigurationBuilder; +import com.securenative.config.SecureNativeOptions; +import com.securenative.enums.EventTypes; +import com.securenative.exceptions.SecureNativeInvalidOptionsException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SDKEventTest { + @Test + @DisplayName("Should throw when creating sdk event invalid user-id") + public void createSDKEventInvalidUserIdThrowTest() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + EventOptions event = new EventOptions(EventTypes.LOG_IN.getType()); + event.setUserId(""); + + assertThrows(SecureNativeInvalidOptionsException.class, () -> { + SDKEvent sdkEvent = new SDKEvent(event, options); + }); + } + + @Test + @DisplayName("Should throw when creating sdk event without user-id") + public void createSDKEventWithoutUserIdThrowTest() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + EventOptions event = new EventOptions(EventTypes.LOG_IN.getType()); + + assertThrows(SecureNativeInvalidOptionsException.class, () -> { + SDKEvent sdkEvent = new SDKEvent(event, options); + }); + } + + @Test + @DisplayName("Should throw when creating sdk event without event type") + public void createSDKEventWithoutEventTypeThrowTest() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + EventOptions event = new EventOptions(""); + + assertThrows(SecureNativeInvalidOptionsException.class, () -> { + SDKEvent sdkEvent = new SDKEvent(event, options); + }); + } +} diff --git a/src/test/java/com/securenative/models/SampleEvent.java b/src/test/java/com/securenative/models/SampleEvent.java new file mode 100644 index 0000000..a28ea88 --- /dev/null +++ b/src/test/java/com/securenative/models/SampleEvent.java @@ -0,0 +1,18 @@ +package com.securenative.models; + +import com.securenative.utils.DateUtils; + +public class SampleEvent implements Event { + private final String eventType = "custom-event"; + private final String timestamp = DateUtils.generateTimestamp(); + + @Override + public String getEventType() { + return eventType; + } + + @Override + public String getTimestamp() { + return timestamp; + } +} diff --git a/src/test/java/com/securenative/utils/DateUtilsTest.java b/src/test/java/com/securenative/utils/DateUtilsTest.java new file mode 100644 index 0000000..ce96d4c --- /dev/null +++ b/src/test/java/com/securenative/utils/DateUtilsTest.java @@ -0,0 +1,31 @@ +package com.securenative.utils; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.time.Instant; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class DateUtilsTest { + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should convert date to iso8601 format") + public void toTimestampTest() { + String iso8601Date = "2000-01-01T00:00:00.000Z"; + Date date = Date.from(Instant.parse(iso8601Date)); + String result = DateUtils.toTimestamp(date); + + Assertions.assertThat(result).isEqualTo(iso8601Date); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should generate current date in iso8601 format") + public void generateTimestampTest() { + String result = DateUtils.generateTimestamp(); + Assertions.assertThat(result).isNotEmpty(); + } +} diff --git a/src/test/java/com/securenative/utils/EncryptionUtilsTest.java b/src/test/java/com/securenative/utils/EncryptionUtilsTest.java new file mode 100644 index 0000000..60e26bd --- /dev/null +++ b/src/test/java/com/securenative/utils/EncryptionUtilsTest.java @@ -0,0 +1,74 @@ +package com.securenative.utils; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; + +public class EncryptionUtilsTest { + private final String SECRET_KEY = "B00C42DAD33EAC6F6572DA756EA4915349C0A4F6"; + private final String PAYLOAD = "{\"cid\":\"198a41ff-a10f-4cda-a2f3-a9ca80c0703b\",\"vi\":\"148a42ff-b40f-4cda-a2f3-a8ca80c0703b\",\"fp\":\"6d8cabd95987f8318b1fe01593d5c2a5.24700f9f1986800ab4fcc880530dd0ed\"}"; + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should encrypt message correctly") + public void encryptTest() { + String result = EncryptionUtils.encrypt(PAYLOAD, SECRET_KEY); + Assertions.assertThat(result).isNotEmpty(); + Assertions.assertThat(result).hasSizeGreaterThan(PAYLOAD.length()); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should decrypt message correctly") + public void decryptTest() { + String encryptedPayload = "5208ae703cc2fa0851347f55d3b76d3fd6035ee081d71a401e8bc92ebdc25d42440f62310bda60628537744ac03f200d78da9e61f1019ce02087b7ce6c976e7b2d8ad6aa978c532cea8f3e744cc6a5cafedc4ae6cd1b08a4ef75d6e37aa3c0c76954d16d57750be2980c2c91ac7ef0bbd0722abd59bf6be22493ea9b9759c3ff4d17f17ab670b0b6fc320e6de982313f1c4e74c0897f9f5a32d58e3e53050ae8fdbebba9009d0d1250fe34dcde1ebb42acbc22834a02f53889076140f0eb8db1"; + String result = EncryptionUtils.decrypt(encryptedPayload, SECRET_KEY); + Assertions.assertThat(result).isEqualTo(PAYLOAD); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should encrypt and decrypt message correctly") + public void encryptDecryptTest() { + String encRes = EncryptionUtils.encrypt(PAYLOAD, SECRET_KEY); + String decRes = EncryptionUtils.decrypt(encRes, SECRET_KEY); + Assertions.assertThat(decRes).isEqualTo(PAYLOAD); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should handle encryption with short key") + public void encryptWithInvalidKeyLenTest() { + String secretKey = "BAD_KEY"; + String result = EncryptionUtils.encrypt(PAYLOAD, secretKey); + Assertions.assertThat(result).hasSize(0); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should handle decryption with short key") + public void decryptWithInvalidKeyLenTest() { + String secretKey = "BAD_KEY"; + String encryptedPayload = "5208ae703cc2fa0851347f55d3b76d3fd6035ee081d71a401e8bc92ebdc25d42440f62310bda60628537744ac03f200d78da9e61f1019ce02087b7ce6c976e7b2d8ad6aa978c532cea8f3e744cc6a5cafedc4ae6cd1b08a4ef75d6e37aa3c0c76954d16d57750be2980c2c91ac7ef0bbd0722abd59bf6be22493ea9b9759c3ff4d17f17ab670b0b6fc320e6de982313f1c4e74c0897f9f5a32d58e3e53050ae8fdbebba9009d0d1250fe34dcde1ebb42acbc22834a02f53889076140f0eb8db1"; + + String result = EncryptionUtils.decrypt(encryptedPayload, secretKey); + + Assertions.assertThat(result).hasSize(0); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Should decrypt with key which is too long") + public void encryptDecryptWithKeyWhichTooLongTest() { + String secretKey = "B00C42DAD33EAC6F6572DA756EA4915349C0A4F6B00C42DAD33EAC6F6572DA756EA4915349C0A4F6"; + + String encRes = EncryptionUtils.encrypt(PAYLOAD, secretKey); + String decRes = EncryptionUtils.decrypt(encRes, secretKey); + + Assertions.assertThat(decRes).isEqualTo(PAYLOAD); + } + +} diff --git a/src/test/java/com/securenative/utils/IPUtilsTest.java b/src/test/java/com/securenative/utils/IPUtilsTest.java new file mode 100644 index 0000000..2a41a71 --- /dev/null +++ b/src/test/java/com/securenative/utils/IPUtilsTest.java @@ -0,0 +1,67 @@ +package com.securenative.utils; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; + +public class IPUtilsTest { + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is valid ipv4") + public void isIpAddressValidIpV4Test() { + String validIPv4 = "172.16.254.1"; + Assertions.assertThat(IPUtils.isIpAddress(validIPv4)).isTrue(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is valid ipv6") + public void isIpAddressValidIpV6Test() { + String validIPv6 = "2001:db8:1234:0000:0000:0000:0000:0000"; + Assertions.assertThat(IPUtils.isIpAddress(validIPv6)).isTrue(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is invalid ipv4") + public void isIpAddressInvalidIpV4Test() { + String validIPv4 = "172.16.2541"; + Assertions.assertThat(IPUtils.isIpAddress(validIPv4)).isFalse(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is invalid ipv6") + public void isIpAddressInvalidIpV6Test() { + String validIPv6 = "2001:db8:1234:0000"; + Assertions.assertThat(IPUtils.isIpAddress(validIPv6)).isFalse(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is valid public ip") + public void isValidPublicIpTest() { + String ip = "64.71.222.37"; + Assertions.assertThat(IPUtils.isValidPublicIp(ip)).isTrue(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is not valid public ip") + public void isNotValidPublicIpTest() { + String ip = "10.0.0.0"; + Assertions.assertThat(IPUtils.isValidPublicIp(ip)).isFalse(); + } + + @Test + @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Check if ip is valid loopback ip") + public void isValidLoopbackIpTest() { + String ip = "127.0.0.1"; + Assertions.assertThat(IPUtils.isLoopBack(ip)).isTrue(); + } +} + diff --git a/src/test/java/com/securenative/utils/RequestUtilsTest.java b/src/test/java/com/securenative/utils/RequestUtilsTest.java new file mode 100644 index 0000000..f530104 --- /dev/null +++ b/src/test/java/com/securenative/utils/RequestUtilsTest.java @@ -0,0 +1,456 @@ +package com.securenative.utils; + +import com.securenative.config.SecureNativeConfigurationBuilder; +import com.securenative.config.SecureNativeOptions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RequestUtilsTest { + + @Test + @DisplayName("Extract ip using proxy headers ipv4") + public void ExtractRequestWithProxyHeadersIPV4() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder() + .withProxyHeaders(new ArrayList<>(Collections.singleton("CF-Connecting-IP"))).build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("CF-Connecting-IP", "203.0.113.1"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("203.0.113.1")); + } + + @Test + @DisplayName("Extract ip using proxy headers ipv6") + public void ExtractRequestWithProxyHeadersIPV6() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder() + .withProxyHeaders(new ArrayList<>(Collections.singleton("CF-Connecting-IP"))).build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("CF-Connecting-IP", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("Extract ip using proxy headers with multiple ipv4") + public void ExtractRequestWithProxyHeadersMultipleIPV4() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder() + .withProxyHeaders(new ArrayList<>(Collections.singleton("CF-Connecting-IP"))).build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("CF-Connecting-IP", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using x-forwarded-for header ipv6") + public void ExtractIpUsingXForwardedForHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-forwarded-for", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using x-forwarded-for header multiple ipv4") + public void ExtractMultipleIpUsingXForwardedForHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-forwarded-for", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using x-client-ip header ipv6") + public void ExtractIpUsingXClientIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-client-ip", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using x-client-ip header multiple ipv4") + public void ExtractMultipleIpUsingXClientIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-client-ip", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using x-real-ip header ipv6") + public void ExtractIpUsingXRealIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-real-ip", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using x-real-ip header multiple ipv4") + public void ExtractMultipleIpUsingXRealIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-real-ip", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using x-forwarded header ipv6") + public void ExtractIpUsingXForwardedHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-forwarded", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using x-forwarded header multiple ipv4") + public void ExtractMultipleIpUsingXForwardedHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-forwarded", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using x-cluster-client-ip header ipv6") + public void ExtractIpUsingXClusterClientIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-cluster-client-ip", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using x-cluster-client-ip header multiple ipv4") + public void ExtractMultipleIpUsingXClusterClientIpHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-cluster-client-ip", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using forwarded-for header ipv6") + public void ExtractIpUsingForwardedForHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("forwarded-for", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using forwarded-for header multiple ipv4") + public void ExtractMultipleIpUsingForwardedForHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("forwarded-for", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using forwarded header ipv6") + public void ExtractIpUsingForwardedHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("forwarded", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using forwarded header multiple ipv4") + public void ExtractMultipleIpUsingForwardedHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("forwarded", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using via header ipv6") + public void ExtractIpUsingViaHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("via", "f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d")); + } + + @Test + @DisplayName("extract ip using via header multiple ipv4") + public void ExtractMultipleIpUsingViaHeader() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("via", "141.246.115.116, 203.0.113.1, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("141.246.115.116")); + } + + @Test + @DisplayName("extract ip using priority with x forwarded for") + public void ExtractIpUsingPriorityWithXForwardedFor() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-forwarded-for", "203.0.113.1"); + request.addHeader("x-real-ip", "198.51.100.101"); + request.addHeader("x-client-ip", "198.51.100.102"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("203.0.113.1")); + } + + @Test + @DisplayName("extract ip using priority without x forwarded for") + public void ExtractIpUsingPriorityWithoutXForwardedFor() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder().build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("x-real-ip", "198.51.100.101"); + request.addHeader("x-client-ip", "203.0.113.1, 141.246.115.116, 12.34.56.3"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + String clientIp = RequestUtils.getClientIpFromRequest(request, headers, options); + + assertThat(clientIp.equals("203.0.113.1")); + } + + @Test + @DisplayName("extract pii data data from headers") + public void ExtractPiiDataFromHeaders() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("Host", "net.example.com"); + request.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)"); + request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + request.addHeader("Accept-Encoding", "gzip,deflate"); + request.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + request.addHeader("Keep-Alive", "300"); + request.addHeader("Connection", "keep-alive"); + request.addHeader("Cookie", "PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120"); + request.addHeader("Pragma", "no-cache"); + request.addHeader("Cache-Control", "no-cache"); + request.addHeader("authorization", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("access_token", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("apikey", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("password", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("passwd", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("secret", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("api_key", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + + Map headers = RequestUtils.getHeadersFromRequest(request, null); + + assertThat(!headers.containsKey("authorization")); + assertThat(!headers.containsKey("access_token")); + assertThat(!headers.containsKey("apikey")); + assertThat(!headers.containsKey("password")); + assertThat(!headers.containsKey("passwd")); + assertThat(!headers.containsKey("secret")); + assertThat(!headers.containsKey("api_key")); + } + + @Test + @DisplayName("extract pii data data from custom headers") + public void ExtractPiiDataFromCustomHeaders() { + ArrayList h = new ArrayList(){ + { + add("authorization"); + add("access_token"); + add("apikey"); + add("password"); + add("passwd"); + add("secret"); + add("api_key"); + } + }; + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder() + .withPiiHeaders(h).build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("Host", "net.example.com"); + request.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)"); + request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + request.addHeader("Accept-Encoding", "gzip,deflate"); + request.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + request.addHeader("Keep-Alive", "300"); + request.addHeader("Connection", "keep-alive"); + request.addHeader("Cookie", "PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120"); + request.addHeader("Pragma", "no-cache"); + request.addHeader("Cache-Control", "no-cache"); + request.addHeader("authorization", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("access_token", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("apikey", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("password", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("passwd", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("secret", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("api_key", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + + Map headers = RequestUtils.getHeadersFromRequest(request, options); + + assertThat(!headers.containsKey("authorization")); + assertThat(!headers.containsKey("access_token")); + assertThat(!headers.containsKey("apikey")); + assertThat(!headers.containsKey("password")); + assertThat(!headers.containsKey("passwd")); + assertThat(!headers.containsKey("secret")); + assertThat(!headers.containsKey("api_key")); + } + + @Test + @DisplayName("extract pii data data from regex pattern") + public void ExtractPiiDataFromRegexPattern() { + SecureNativeOptions options = SecureNativeConfigurationBuilder.defaultConfigBuilder() + .withPiiRegexPattern("((?i)(http_auth_)(\\w+)?)").build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName("www.securenative.com"); + request.addHeader("Host", "net.example.com"); + request.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)"); + request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + request.addHeader("Accept-Encoding", "gzip,deflate"); + request.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + request.addHeader("Keep-Alive", "300"); + request.addHeader("Connection", "keep-alive"); + request.addHeader("Cookie", "PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120"); + request.addHeader("Pragma", "no-cache"); + request.addHeader("Cache-Control", "no-cache"); + request.addHeader("http_auth_authorization", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_access_token", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_apikey", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_password", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_passwd", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_secret", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + request.addHeader("http_auth_api_key", "ylSkZIjbdWybfs4fUQe9BqP0LH5Z"); + + Map headers = RequestUtils.getHeadersFromRequest(request, options); + + assertThat(!headers.containsKey("http_auth_authorization")); + assertThat(!headers.containsKey("http_auth_access_token")); + assertThat(!headers.containsKey("http_auth_apikey")); + assertThat(!headers.containsKey("http_auth_password")); + assertThat(!headers.containsKey("http_auth_passwd")); + assertThat(!headers.containsKey("http_auth_secret")); + assertThat(!headers.containsKey("http_auth_api_key")); + } +} diff --git a/src/test/java/com/securenative/utils/SignatureUtilsTest.java b/src/test/java/com/securenative/utils/SignatureUtilsTest.java new file mode 100644 index 0000000..e0dc464 --- /dev/null +++ b/src/test/java/com/securenative/utils/SignatureUtilsTest.java @@ -0,0 +1,30 @@ +package com.securenative.utils; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; + +public class SignatureUtilsTest { + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Verify request signature") + public void verifyRequestPayloadTest() { + String signature = "c4574c1748064735513697750c6223ff36b03ae3b85b160ce8788557d01e1d9d1c9cd942074323ee0061d3dcc8c94359c5acfa6eee8e2da095b3967b1a88ab73"; + String payload = "{\"id\":\"4a9157ffbd18cfbd73a57298\",\"type\":\"security-action\",\"flow\":{\"id\":\"62298c73a9bb433fbd1f75984a9157fd\",\"name\":\"Block user that violates geo velocity\"},\"userId\":\"73a9bb433fbd1f75984a9157\",\"userTraits\":{\"name\":\"John Doe\",\"email\":\"john.doe@gmail.com\"},\"request\":{\"ip\":\"10.0.0.0\",\"fp\":\"9bb433fb984a9157d1f7598\"},\"action\":\"block\",\"properties\":{\"type\":\"customer\"},\"timestamp\":\"2020-02-23T22:28:55.387Z\"}"; + String secretKey = "B00C42DAD33EAC6F6572DA756EA4915349C0A4F6"; + + Boolean result = SignatureUtils.isValidSignature(signature, payload, secretKey); + Assertions.assertThat(result).isTrue(); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + @DisplayName("Verify request with empty signature") + public void verifyRequestEmptySignatureTest() { + Boolean result = SignatureUtils.isValidSignature("", "", "B00C42DAD33EAC6F6572DA756EA4915349C0A4F6"); + Assertions.assertThat(result).isFalse(); + } +} diff --git a/src/test/java/com/securenative/utils/VersionTest.java b/src/test/java/com/securenative/utils/VersionTest.java new file mode 100644 index 0000000..1611bf7 --- /dev/null +++ b/src/test/java/com/securenative/utils/VersionTest.java @@ -0,0 +1,34 @@ +package com.securenative.utils; + +import com.securenative.ResourceStreamImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class VersionTest { + + @Test + public void testVersionExtraction() throws IOException { + + String props = String.join(System.getProperty("line.separator"), + "version=1.0.0", + "groupId=com.securenative.java", + "artifactId=securenative-java"); + + InputStream inputStream = new ByteArrayInputStream(props.getBytes()); + ResourceStreamImpl resourceStream = Mockito.spy(new ResourceStreamImpl()); + Mockito.when(resourceStream.getInputStream("/META-INF/maven/com.securenative.java/securenative-java/pom.properties")).thenReturn(inputStream); + + VersionUtils.setResourceStream(resourceStream); + + assertThat(VersionUtils.getVersion()).isEqualTo("1.0.0"); + + // restore resource stream + VersionUtils.setResourceStream(new ResourceStreamImpl()); + } +} diff --git a/src/test/resources/securenative.properties b/src/test/resources/securenative.properties new file mode 100644 index 0000000..53a61e1 --- /dev/null +++ b/src/test/resources/securenative.properties @@ -0,0 +1,2 @@ +SECURENATIVE_API_KEY=SOME_API_KEY +SECURENATIVE_TIMEOUT=2000 \ No newline at end of file