├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── gradle.yml ├── .gitignore ├── .idea ├── .gitignore ├── checkstyle-idea.xml ├── codeStyles │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ └── SailPoint.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── modules │ ├── openid-sse-model.iml │ ├── openid-sse-model.main.iml │ └── openid-sse-model.test.iml ├── sse-openid-model.iml ├── uiDesigner.xml └── vcs.xml ├── DCO.txt ├── KEYS.txt ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── verification-keyring.gpg ├── verification-metadata.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ ├── com │ └── sailpoint │ │ └── sse │ │ └── model │ │ ├── DeliveryMethods.java │ │ ├── NonstandardSSEvent.java │ │ ├── SEToken.java │ │ ├── SSEvent.java │ │ ├── SSEventTypes.java │ │ ├── StreamConfiguration.java │ │ ├── StreamStatus.java │ │ ├── TransmitterConfig.java │ │ ├── Utils.java │ │ ├── ValidationException.java │ │ ├── caep │ │ ├── CAEPAssuranceLevelChange.java │ │ ├── CAEPAssuranceLevelChangeDirection.java │ │ ├── CAEPBaseEvent.java │ │ ├── CAEPChangeType.java │ │ ├── CAEPComplianceStatus.java │ │ ├── CAEPCredentialChange.java │ │ ├── CAEPCredentialType.java │ │ ├── CAEPDeviceComplianceChange.java │ │ ├── CAEPIPAddrChanged.java │ │ ├── CAEPInitiatingEntity.java │ │ ├── CAEPSessionRevoked.java │ │ ├── CAEPStreamUpdated.java │ │ ├── CAEPTokenClaimsChange.java │ │ └── NISTAuthenticatorAssuranceLevel.java │ │ ├── risc │ │ ├── RISCAccountCredentialChangeRequired.java │ │ ├── RISCAccountDisabled.java │ │ ├── RISCAccountDisabledReasons.java │ │ ├── RISCAccountEnabled.java │ │ ├── RISCAccountPurged.java │ │ ├── RISCIdentifierChanged.java │ │ ├── RISCIdentifierRecycled.java │ │ ├── RISCOptIn.java │ │ ├── RISCOptOutCancelled.java │ │ ├── RISCOptOutEffective.java │ │ ├── RISCOptOutInitiated.java │ │ ├── RISCRecoveryActivated.java │ │ ├── RISCRecoveryInformationChanged.java │ │ └── RISCSessionsRevoked.java │ │ └── sse │ │ ├── SSEStreamUpdated.java │ │ └── SSEVerification.java │ └── module-info.java └── test └── java └── com └── sailpoint └── sse └── model ├── CAEPAssuranceLevelChangeTest.java ├── CAEPCredentialChangeTest.java ├── CAEPDeviceComplianceChangeTest.java ├── CAEPSessionRevokedTest.java ├── CAEPTokenClaimsChangeTest.java ├── EventStreamTests.java ├── GoogleTests.java ├── OpenIDSSEProfileTest.java ├── RISCProfileTests.java └── TransmitterConfigTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gpg binary 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Submit a bug for this project. 4 | title: "[BUG] Your bug title here" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature or an idea for this project 4 | title: "[FEATURE] Your feature request title" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '33 23 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'java' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.9 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.9 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Backup files 2 | *~ 3 | 4 | # Java 5 | # Compiled class file 6 | *.class 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.nar 12 | *.ear 13 | *.zip 14 | *.tar.gz 15 | *.rar 16 | 17 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 18 | hs_err_pid* 19 | 20 | 21 | # Gradle 22 | .gradle 23 | **/build/ 24 | !src/**/build/ 25 | 26 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 27 | !gradle-wrapper.jar 28 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml 3 | sonarlint/ 4 | checkstyleidea-libs/ 5 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/copyright/SailPoint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules/openid-sse-model.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules/openid-sse-model.main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules/openid-sse-model.test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/sse-openid-model.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DCO.txt: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. -------------------------------------------------------------------------------- /KEYS.txt: -------------------------------------------------------------------------------- 1 | This file contains the PGP keys of various developers. 2 | 3 | Users: gpg --import KEYS.txt 4 | Developers: 5 | (gpg --list-sigs 6 | && gpg --armor --export ) >> this file. 7 | 8 | pub rsa4096 2021-03-04 [SC] 9 | 1C4DB726ED7B55A3288F094AE9F1FF673441FD20 10 | uid [ultimate] Adam E. Hampton (Principal Engineer, SailPoint Technologies) 11 | sig 3 E9F1FF673441FD20 2021-03-04 Adam E. Hampton (Principal Engineer, SailPoint Technologies) 12 | sig 453785727F5767D9 2021-03-04 Matt Domsch (SailPoint VP, Engineering Fellow) 13 | sub rsa4096 2021-03-04 [E] 14 | sig E9F1FF673441FD20 2021-03-04 Adam E. Hampton (Principal Engineer, SailPoint Technologies) 15 | 16 | -----BEGIN PGP PUBLIC KEY BLOCK----- 17 | 18 | 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 55 | 5cE= 56 | =8ydQ 57 | -----END PGP PUBLIC KEY BLOCK----- 58 | pub rsa4096 2021-02-24 [SC] 59 | FA5C0CC71B18B0FAA2ED5E1A453785727F5767D9 60 | uid [ full ] Matt Domsch (SailPoint VP, Engineering Fellow) 61 | sig 3 453785727F5767D9 2021-02-24 Matt Domsch (SailPoint VP, Engineering Fellow) 62 | sig E9F1FF673441FD20 2021-03-11 Adam E. Hampton (Principal Engineer, SailPoint Technologies) 63 | sub rsa4096 2021-02-24 [E] 64 | sig 453785727F5767D9 2021-02-24 Matt Domsch (SailPoint VP, Engineering Fellow) 65 | 66 | -----BEGIN PGP PUBLIC KEY BLOCK----- 67 | 68 | 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 174 | HMyejA== 175 | =/c/K 176 | -----END PGP PUBLIC KEY BLOCK----- 177 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openid-sse-model 2 | 3 | An implementation of the [OpenID Foundation](https://openid.net/wg/sse/)'s Shared Signals and Events (SSE) data model for the Continuous Access Evaluation Protocol (CAEP) and Risk Incident Sharing and Coordination (RISC) event profiles. 4 | 5 | This library provides classes implementing SSE (both CAEP and RISC profiles) under a Java environment. It includes the 6 | following dependences: 7 | 8 | - com.nimbusds nimbus-jose-jwt for JSONObject and JWTClaimsSet 9 | 10 | ## Examples 11 | 12 | Producing a Security Event Token using this library involves constructing the objects representing the Subject 13 | Identifier, the Shared Signals Event, and the Security Event Token that will carry the event. There are specific event 14 | classes for each defined RISC and CAEP event. Construction follows the builder pattern. Events each have a validate() 15 | method to verify mandatory fields. 16 | 17 | OpaqueSubjectIdentifier session = new OpaqueSubjectIdentifier.Builder() 18 | .id("dMTlD|1600802906337.16|16008.16") 19 | .build(); 20 | 21 | SubjectIdentifier user = new SubjectIdentifier.Builder() 22 | .format(IdentifierFormats.ISSUER_SUBJECT) 23 | .issuer("https://idp.example.com/123456789/") 24 | .subject("dMTlD|1600802906337.16|16008.16") 25 | .build(); 26 | 27 | OpaqueSubjectIdentifier tenant = new OpaqueSubjectIdentifier.Builder() 28 | .id("123456789") 29 | .build(); 30 | 31 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 32 | .session(session) 33 | .user(user) 34 | .tenant(tenant) 35 | .build(); 36 | 37 | final long now = System.currentTimeMillis() /1000; 38 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 39 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 40 | .reasonAdmin("Landspeed Policy Violation: C076E82F") 41 | .reasonUser("Access attempt from multiple regions.") 42 | .eventTimestamp(now) 43 | .subject(subj) 44 | .build(); 45 | evt.validate(); 46 | 47 | JWTClaimsSet set = new JWTClaimsSet.Builder() 48 | .issuer("https://idp.example.com/123456789/") 49 | .jwtID(UUID.randomUUID().toString()) 50 | .issueTime(DateUtils.fromSecondsSinceEpoch(now)) 51 | .audience("https://sp.example.com/caep") 52 | .claim(SEToken.EVENTS_CLAIM, evt) 53 | .build(); 54 | 55 | See more usage examples in `src/test`. 56 | 57 | ## Compiling 58 | 59 | This library is implemented as a Gradle based java library. Java 9+ is required. Running: 60 | 61 | ./gradlew build 62 | 63 | produces a versioned .jar file in the build/libs directory: 64 | 65 | ls -ltr build/libs 66 | total 700 67 | -rwxrwxrwx 1 mdomsch mdomsch 64318 Mar 10 22:33 openid-sse-model-0.1.0-SNAPSHOT.jar 68 | -rwxrwxrwx 1 mdomsch mdomsch 616249 Mar 10 22:33 openid-sse-model-0.1.0-SNAPSHOT-javadoc.jar 69 | -rwxrwxrwx 1 mdomsch mdomsch 28791 Mar 10 22:33 openid-sse-model-0.1.0-SNAPSHOT-sources.jar 70 | 71 | ## Testing 72 | 73 | The library has tests implemented in `/src/test/java/` and are run with Gradle: 74 | 75 | ./gradlew test 76 | 77 | ## Incorporation into your own projects 78 | 79 | The library is published to Maven Central, and may be incorporated into your own projects as a dependency. 80 | Because the library is under active and frequent development, you may wish to pull from the OSSRH 81 | snapshot repository as added here: 82 | 83 | `build.gradle` 84 | 85 | repositories { 86 | mavenCentral() 87 | maven { 88 | url "https://s01.oss.sonatype.org/content/repositories/releases/" 89 | } 90 | maven { 91 | url "https://s01.oss.sonatype.org/content/repositories/snapshots/" 92 | } 93 | } 94 | 95 | dependencies { 96 | implementation group: 'com.sailpoint', name: 'openid-sse-model', version: '0.2.0' 97 | } 98 | 99 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | plugins { 8 | id 'java-library' 9 | id 'maven-publish' 10 | id 'signing' 11 | } 12 | 13 | version = '0.3.0-SNAPSHOT' 14 | 15 | repositories { 16 | mavenCentral() 17 | maven { 18 | url "https://s01.oss.sonatype.org/content/repositories/releases/" 19 | } 20 | maven { 21 | url "https://s01.oss.sonatype.org/content/repositories/snapshots/" 22 | } 23 | flatDir { 24 | dirs 'libs' 25 | } 26 | } 27 | 28 | dependencies { 29 | 30 | // Nimbus JOSE JWT 31 | // https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt 32 | api group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '9.16-preview.1' 33 | implementation group: 'com.sailpoint', name: 'ietf-subject-identifiers-model', version: '0.2.0-SNAPSHOT' 34 | //implementation files("../ietf-subject-identifiers-model/build/libs/ietf-subject-identifiers-model-0.2.0-SNAPSHOT.jar") 35 | // Use JUnit test framework 36 | testImplementation 'junit:junit:4.13.2' 37 | } 38 | 39 | allprojects { 40 | gradle.projectsEvaluated { 41 | tasks.withType(JavaCompile) { 42 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 43 | } 44 | } 45 | } 46 | 47 | java { 48 | modularity.inferModulePath = true 49 | withJavadocJar() 50 | withSourcesJar() 51 | } 52 | 53 | ext.isReleaseVersion = !version.endsWith("SNAPSHOT") 54 | 55 | publishing { 56 | publications { 57 | model(MavenPublication) { 58 | from components.java 59 | pom { 60 | name = 'openid-sse-model' 61 | description = 'OpenID Shared Signals and Events models' 62 | url = 'http://github.com/sailpoint-oss/openid-sse-model' 63 | 64 | licenses { 65 | license { 66 | name = 'The Apache License, Version 2.0' 67 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 68 | } 69 | } 70 | developers { 71 | developer { 72 | id = 'adam-hampton-sp' 73 | name = 'Adam Hampton' 74 | email = 'adam.hampton@sailpoint.com' 75 | } 76 | developer { 77 | id = 'matt-domsch-sp' 78 | name = 'Matt Domsch' 79 | email = 'matt.domsch@sailpoint.com' 80 | } 81 | } 82 | scm { 83 | connection = 'scm:git:git://github.com/sailpoint-oss/openid-sse-model.git' 84 | developerConnection = 'scm:git:ssh://github.com/sailpoint-oss/openid-sse-model.git' 85 | url = 'https://github.com/sailpoint-oss/openid-sse-model/' 86 | } 87 | } 88 | } 89 | } 90 | 91 | repositories { 92 | maven { 93 | name = 'myRepo' 94 | url = "file://${buildDir}/repo" 95 | } 96 | maven { 97 | def releaseRepo = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 98 | def snapshotRepo = "https://s01.oss.sonatype.org/content/repositories/snapshots/" 99 | url = isReleaseVersion ? releaseRepo : snapshotRepo 100 | credentials { 101 | username = project.hasProperty('ossrhUsername') ? ossrhUsername : "Unknown user" 102 | password = project.hasProperty('ossrhPassword') ? ossrhPassword : "Unknown password" 103 | } 104 | } 105 | } 106 | } 107 | 108 | javadoc { 109 | if (JavaVersion.current().isJava9Compatible()) { 110 | options.addBooleanOption('html5', true) 111 | } 112 | } 113 | 114 | signing { 115 | useGpgCmd() 116 | sign publishing.publications.model 117 | } 118 | 119 | group = 'com.sailpoint' 120 | sourceCompatibility = 9; 121 | targetCompatibility = 9; 122 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.dependency.verification=lenient -------------------------------------------------------------------------------- /gradle/verification-keyring.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sailpoint-oss/openid-sse-model/d279866dd5628bfe9a587c30b3e9f8ce45c305b1/gradle/verification-keyring.gpg -------------------------------------------------------------------------------- /gradle/verification-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sailpoint-oss/openid-sse-model/d279866dd5628bfe9a587c30b3e9f8ce45c305b1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'openid-sse-model' 11 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/DeliveryMethods.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Commonly used SSE delivery methods for SSE/RISC/CAEP events. 14 | */ 15 | public enum DeliveryMethods { 16 | 17 | PUSH("push"), 18 | POLL("poll"); 19 | 20 | // Name space prefix string for Shared Signals and Event types. 21 | public static final String SSE_PREFIX = "https://schemas.openid.net/secevent/risc/delivery-method/"; 22 | private static final Map BY_NAME = new HashMap<>(); 23 | 24 | static { 25 | for (DeliveryMethods t : values()) { 26 | BY_NAME.put(t.name, t); 27 | } 28 | } 29 | 30 | private final String name; 31 | 32 | DeliveryMethods(final String s) { 33 | name = SSE_PREFIX + s; 34 | } 35 | 36 | public static DeliveryMethods valueOfLabel(final String name) { 37 | return BY_NAME.get(name); 38 | } 39 | 40 | public static boolean contains(final String name) { 41 | return BY_NAME.containsKey(name); 42 | } 43 | 44 | public boolean equalsName(final String otherName) { 45 | return name.equals(otherName); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return this.name; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/NonstandardSSEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | 11 | import java.text.ParseException; 12 | 13 | public class NonstandardSSEvent extends SSEvent { 14 | 15 | public NonstandardSSEvent(final String eventName) { 16 | super(); 17 | setEventTypeName(eventName); 18 | } 19 | public NonstandardSSEvent() { 20 | super(); 21 | } 22 | 23 | @Override 24 | public void validate() throws ParseException, SIValidationException, ValidationException { 25 | validateSubject(); 26 | } 27 | 28 | public static class Builder extends SSEvent.Builder { 29 | 30 | private String eventName; 31 | 32 | public Builder() { super(); } 33 | 34 | @Override 35 | protected NonstandardSSEvent createObj() { 36 | return new NonstandardSSEvent(); 37 | } 38 | 39 | protected Builder getThis() { 40 | return this; 41 | } 42 | 43 | public Builder eventName(final String name) { 44 | eventName = name; 45 | return this; 46 | } 47 | 48 | @Override 49 | public NonstandardSSEvent build() { 50 | NonstandardSSEvent t = createObj(); 51 | t.setEventTypeName(eventName); 52 | t.put(t.getEventTypeName(), members); 53 | return t; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/SEToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jwt.JWTClaimsSet; 11 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 12 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 13 | 14 | import java.lang.reflect.Constructor; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.text.ParseException; 17 | import java.util.Map; 18 | 19 | public class SEToken { 20 | public static final String EVENTS_CLAIM = "events"; 21 | private static final String TYP_CLAIM = "typ"; 22 | private static final String EXP_CLAIM = "exp"; 23 | private static final String TYP_VALUE = "secevent+jwt"; 24 | 25 | private SEToken() { 26 | } 27 | 28 | /* openid-sse-profile-1_0.txt section 11.1.4 */ 29 | private static void validateTyp(final JWTClaimsSet set) throws ParseException, ValidationException { 30 | String typ; 31 | typ = set.getStringClaim(TYP_CLAIM); 32 | 33 | if (null == typ) { 34 | /* OK to be missing */ 35 | return; 36 | } 37 | 38 | if (!TYP_VALUE.equals(typ)) { 39 | throw new ValidationException("SET events claim typ value must be " + TYP_VALUE + "."); 40 | } 41 | } 42 | 43 | /* openid-sse-profile-1_0.txt section 11.1.5 */ 44 | private static void validateExp(final JWTClaimsSet set) throws ValidationException { 45 | Object exp; 46 | exp = set.getClaim(EXP_CLAIM); 47 | if (null != exp) { 48 | throw new ValidationException("SET events must not have an exp claim."); 49 | } 50 | } 51 | 52 | /* openid-sse-profile-1_0.txt section 11.1.7 */ 53 | private static void validateEventsClaim(final JWTClaimsSet set) throws ParseException, ValidationException { 54 | Map o = set.getJSONObjectClaim(EVENTS_CLAIM); 55 | 56 | if (null == o) { 57 | throw new ValidationException("SETs must contain an events member."); 58 | } 59 | 60 | if (o.size() > 1) { 61 | throw new ValidationException("SET events member must contain exactly one event."); 62 | } 63 | } 64 | 65 | public static void validate(final JWTClaimsSet set) throws ParseException, ValidationException { 66 | validateTyp(set); 67 | validateExp(set); 68 | validateEventsClaim(set); 69 | } 70 | 71 | 72 | 73 | /** 74 | * Parse a JSON String into a fully fleshed sse.model class hierarchy 75 | * @param jsonString - representing a JSON object "{ ... }" 76 | * @return a JWTClaimsSet wherein all members have been converted to sse.model classes 77 | * @throws ParseException when JSONObject parsing fails 78 | * @throws ValidationException when SSE validation rules fail 79 | * @throws SIValidationException when SubjectIdentifier validation rules fail 80 | */ 81 | 82 | public static JWTClaimsSet parse(final String jsonString) throws ParseException, SIValidationException, ValidationException { 83 | SSEvent event; 84 | Constructor ctor; 85 | Class cls; 86 | 87 | JWTClaimsSet set = JWTClaimsSet.parse(jsonString); 88 | SEToken.validate(set); 89 | 90 | JSONObject eventsClaim = new JSONObject(set.getJSONObjectClaim(SEToken.EVENTS_CLAIM)); 91 | // There can be exactly one event present after the above validation 92 | String eventName = eventsClaim.keySet().iterator().next(); 93 | SSEventTypes eventType = SSEventTypes.enumByName(eventName); 94 | 95 | if (null != eventType) { 96 | cls = eventType.getCls(); 97 | } else { 98 | cls = NonstandardSSEvent.class; 99 | } 100 | 101 | try { 102 | ctor = cls.getConstructor(); 103 | } catch (NoSuchMethodException e) { 104 | throw new ValidationException("SEToken Parser cannot find a constructor() for " + cls.getName()); 105 | } 106 | 107 | // event is an instance of the specific event type by this eventName URL 108 | try { 109 | event = ctor.newInstance(); 110 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 111 | throw new ValidationException("SEToken Parser cannot instantiate matching class for event type" + eventName); 112 | } 113 | event.setEventType(eventType); 114 | event.setEventTypeName(eventName); 115 | 116 | // Convert the incoming event subject into a SubjectIdentifier hierarchy 117 | JSONObject eventDetailsJO = (JSONObject) eventsClaim.get(eventName); 118 | JSONObject subjectJO = (JSONObject) eventDetailsJO.get(SSEvent.SUBJECT_MEMBER); 119 | SubjectIdentifier subj = SubjectIdentifier.convertSubjects(subjectJO); 120 | eventDetailsJO.put(SSEvent.SUBJECT_MEMBER, subj); 121 | event.merge(eventsClaim); 122 | 123 | // From this point down, all members are base Java types (Strings, Longs) or more JSONObjects 124 | // so we shouldn't need to parse further. 125 | event.validate(); 126 | 127 | JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(set) 128 | .claim(SEToken.EVENTS_CLAIM, event); 129 | 130 | JWTClaimsSet newSet = builder.build(); 131 | SEToken.validate(newSet); 132 | return newSet; 133 | } 134 | 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/SSEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 11 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 12 | 13 | import java.text.ParseException; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | 17 | /** 18 | * A Shared Signals Event (SSE Event). 19 | * 20 | * @author adam.hampton 21 | * @author matt.domsch 22 | */ 23 | public abstract class SSEvent extends JSONObject { 24 | 25 | public static final String SUBJECT_MEMBER = "subject"; 26 | private static final String REASON_MEMBER = "reason"; 27 | private static final String PROPERTIES_MEMBER = "properties"; 28 | 29 | private SSEventTypes eventType; 30 | private String eventTypeName; 31 | 32 | protected SSEvent() { 33 | } 34 | 35 | public SSEventTypes getEventType() { 36 | return eventType; 37 | } 38 | 39 | public String getEventTypeName() { 40 | return eventTypeName; 41 | } 42 | public void setEventTypeName(final String name) { 43 | eventTypeName = name; 44 | } 45 | 46 | public void setEventType(final SSEventTypes eventType) { 47 | this.eventType = eventType; 48 | if (null != eventType) { 49 | this.eventTypeName = eventType.toString(); 50 | } 51 | } 52 | 53 | protected void validateEventTypeName() throws ValidationException { 54 | for (String k : this.keySet()) { 55 | if (SSEventTypes.contains(k)) { 56 | return; 57 | } 58 | } 59 | throw new ValidationException("SSEvent eventTypeName not in SSEventTypes."); 60 | } 61 | 62 | protected void validateSubject() throws ParseException, SIValidationException, ValidationException { 63 | if (null == eventTypeName) { 64 | /* Unknown event type, not instantiated via a normal constructor. */ 65 | return; 66 | } 67 | JSONObject members = (JSONObject) get(eventTypeName); 68 | if (null == members) { 69 | throw new ValidationException("SSE Events must have a container Map whose key is the event type URI"); 70 | } 71 | 72 | // SSE Events may have a simple subject in the JWT claims "sub" instead, which we wouldn't know about here 73 | // Therefore subj may be null in this event. 74 | SubjectIdentifier subj = getSubjectIdentifier(); 75 | if (null != subj) { subj.validate(); } 76 | } 77 | 78 | public Object getMember(final String member) { 79 | JSONObject members = (JSONObject) get(getEventTypeName()); 80 | if (null == members) { 81 | return null; 82 | } 83 | 84 | if (!members.containsKey(member)) { 85 | return null; 86 | } 87 | return members.get(member); 88 | } 89 | // Used when we are parsing and thus do not know we have a SubjectIdentifier there 90 | public final JSONObject getSubjectIdentifierJO() { 91 | return (JSONObject) getMember(SUBJECT_MEMBER); 92 | } 93 | 94 | 95 | public final SubjectIdentifier getSubjectIdentifier() { 96 | return (SubjectIdentifier) getMember(SUBJECT_MEMBER); 97 | } 98 | 99 | 100 | public void validate() throws ParseException, SIValidationException, ValidationException { 101 | validateSubject(); 102 | } 103 | 104 | @Override 105 | public boolean equals(final Object obj) { 106 | if (!super.equals(obj)) { 107 | return false; 108 | } 109 | SSEvent event = (SSEvent) obj; 110 | return eventType.equals(event.getEventType()); 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | return Objects.hash(this, eventType); 116 | } 117 | 118 | protected abstract static class Builder> { 119 | 120 | protected final T members; 121 | protected final B thisObj; 122 | private SSEventTypes eventType; 123 | 124 | protected Builder() { 125 | members = createObj(); 126 | thisObj = getThis(); 127 | } 128 | 129 | protected Builder(final SSEventTypes eventType) { 130 | members = createObj(); 131 | thisObj = getThis(); 132 | this.eventType(eventType); 133 | } 134 | 135 | public T build() { 136 | T t = createObj(); 137 | t.setEventType(eventType); 138 | t.put(eventType.toString(), members); 139 | return t; 140 | } 141 | 142 | protected abstract T createObj(); 143 | 144 | protected abstract B getThis(); 145 | 146 | public B eventType(final SSEventTypes eventType) { 147 | this.eventType = eventType; 148 | return thisObj; 149 | } 150 | 151 | public B subject(final Map sub) { 152 | members.put(SUBJECT_MEMBER, sub); 153 | return thisObj; 154 | } 155 | 156 | public B reason(final String reason) { 157 | members.put(REASON_MEMBER, reason); 158 | return thisObj; 159 | } 160 | 161 | public B properties(final Map properties) { 162 | members.put(PROPERTIES_MEMBER, properties); 163 | return thisObj; 164 | } 165 | 166 | public B member(final String key, final Object o) { 167 | members.put(key, o); 168 | return thisObj; 169 | } 170 | } 171 | } 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/SSEventTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.sailpoint.sse.model.caep.*; 10 | import com.sailpoint.sse.model.risc.*; 11 | import com.sailpoint.sse.model.sse.*; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | 17 | public enum SSEventTypes { 18 | 19 | RISC_ACCOUNT_CREDENTIAL_CHANGE_REQUIRED("account-credential-change-required", SSESpecs.RISC_PREFIX, RISCAccountCredentialChangeRequired.class), 20 | RISC_ACCOUNT_PURGED("account-purged", SSESpecs.RISC_PREFIX, RISCAccountPurged.class), 21 | RISC_ACCOUNT_DISABLED("account-disabled", SSESpecs.RISC_PREFIX, RISCAccountDisabled.class), 22 | RISC_ACCOUNT_ENABLED("account-enabled", SSESpecs.RISC_PREFIX, RISCAccountEnabled.class), 23 | RISC_IDENTIFIER_CHANGED("identifier-changed", SSESpecs.RISC_PREFIX, RISCIdentifierChanged.class), 24 | RISC_IDENTIFIER_RECYCLED("identifier-recycled", SSESpecs.RISC_PREFIX, RISCIdentifierRecycled.class), 25 | RISC_OPT_IN("opt-in", SSESpecs.RISC_PREFIX, RISCOptIn.class), 26 | RISC_OPT_OUT_INITIATED("opt-out-initiated", SSESpecs.RISC_PREFIX, RISCOptOutInitiated.class), 27 | RISC_OPT_OUT_CANCELLED("opt-out-cancelled", SSESpecs.RISC_PREFIX, RISCOptOutCancelled.class), 28 | RISC_OPT_OUT_EFFECTIVE("opt-out-effective", SSESpecs.RISC_PREFIX, RISCOptOutEffective.class), 29 | RISC_RECOVERY_ACTIVATED("recovery-activated", SSESpecs.RISC_PREFIX, RISCRecoveryActivated.class), 30 | RISC_RECOVERY_INFORMATION_CHANGED("recovery-information-changed", SSESpecs.RISC_PREFIX, RISCRecoveryInformationChanged.class), 31 | RISC_SESSIONS_REVOKED("sessions-revoked", SSESpecs.RISC_PREFIX, RISCSessionsRevoked.class), 32 | 33 | CAEP_IPADDR_CHANGED("ip-address-changed", SSESpecs.CAEP_PREFIX, CAEPIPAddrChanged.class), 34 | //Where is this defined? 35 | //CAEP_TOKEN_REVOCATION("token-revocation", SSESpecs.CAEP_PREFIX.class), 36 | CAEP_SESSION_REVOKED("session-revoked", SSESpecs.CAEP_PREFIX, CAEPSessionRevoked.class), 37 | CAEP_TOKEN_CLAIMS_CHANGE("token-claims-change", SSESpecs.CAEP_PREFIX, CAEPTokenClaimsChange.class), 38 | CAEP_CREDENTIAL_CHANGE("credential-change", SSESpecs.CAEP_PREFIX, CAEPCredentialChange.class), 39 | CAEP_ASSURANCE_LEVEL_CHANGE("assurance-level-change", SSESpecs.CAEP_PREFIX, CAEPAssuranceLevelChange.class), 40 | CAEP_DEVICE_COMPLIANCE_CHANGE("device-compliance-change", SSESpecs.CAEP_PREFIX, CAEPDeviceComplianceChange.class), 41 | 42 | SSE_VERIFICATION("verification", SSESpecs.SSE_PREFIX, SSEVerification.class), 43 | SSE_STREAM_UPDATED("stream-updated", SSESpecs.SSE_PREFIX, SSEStreamUpdated.class); 44 | 45 | // Name space prefix string for Shared Signals and Event types. 46 | private static final String SSE_URL_PREFIX = "https://schemas.openid.net/secevent/"; 47 | private static final Map BY_NAME = new HashMap<>(); 48 | 49 | static { 50 | for (SSEventTypes t : values()) { 51 | BY_NAME.put(t.name, t); 52 | } 53 | } 54 | 55 | private final String name; 56 | private final Class cls; 57 | 58 | SSEventTypes(final String s, final String spec, final Class cls) { 59 | name = SSE_URL_PREFIX + spec + "/event-type/" + s; 60 | this.cls = cls; 61 | } 62 | 63 | public static SSEventTypes enumByName(String name) { 64 | return BY_NAME.get(name); 65 | } 66 | 67 | public static boolean contains(final String name) { 68 | return BY_NAME.containsKey(name); 69 | } 70 | 71 | public boolean equalsName(String otherName) { 72 | return name.equals(otherName); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return this.name; 78 | } 79 | 80 | public Class getCls() { return this.cls; } 81 | 82 | } 83 | 84 | /** 85 | * Commonly used SSE event types for SSE/CAEP events. 86 | */ 87 | class SSESpecs { 88 | public static final String RISC_PREFIX = "risc"; 89 | public static final String SSE_PREFIX = "sse"; 90 | public static final String CAEP_PREFIX = "caep"; 91 | 92 | private SSESpecs() { 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/StreamConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | 11 | import java.util.Arrays; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | 15 | public class StreamConfiguration extends JSONObject { 16 | 17 | private static final String ISSUER_MEMBER = "iss"; 18 | private static final String AUDIENCE_MEMBER = "aud"; 19 | private static final String EVENTS_SUPPORTED_MEMBER = "events_supported"; 20 | private static final String EVENTS_REQUESTED_MEMBER = "events_requested"; 21 | private static final String EVENTS_DELIVERED_MEMBER = "events_delivered"; 22 | private static final String DELIVERY_MEMBER = "delivery"; 23 | private static final String MIN_VERIFICATION_INTERVAL_MEMBER = "min_verification_interval"; 24 | private static final String FORMAT_MEMBER = "format"; 25 | 26 | private static final HashSet READ_ONLY_MEMBERS = new HashSet<>(Arrays.asList( 27 | ISSUER_MEMBER, 28 | AUDIENCE_MEMBER, 29 | EVENTS_SUPPORTED_MEMBER, 30 | EVENTS_DELIVERED_MEMBER, 31 | MIN_VERIFICATION_INTERVAL_MEMBER)); 32 | 33 | @Override 34 | public Object put(final String key, final Object value) throws UnsupportedOperationException { 35 | if (READ_ONLY_MEMBERS.contains(key)) { 36 | throw new UnsupportedOperationException(String.format("StreamConfiguration member %s is read-only", key)); 37 | } 38 | return super.put(key, value); 39 | } 40 | 41 | protected Object superPut(final String key, final Object value) { 42 | return super.put(key, value); 43 | } 44 | 45 | public static class Builder { 46 | 47 | private static final StreamConfiguration sc = new StreamConfiguration(); 48 | 49 | public Builder issuer(final String iss) { 50 | sc.superPut(ISSUER_MEMBER, iss); 51 | return this; 52 | } 53 | 54 | public Builder audience(final String aud) { 55 | sc.superPut(AUDIENCE_MEMBER, aud); 56 | return this; 57 | } 58 | 59 | public Builder audience(final List aud) { 60 | sc.superPut(AUDIENCE_MEMBER, aud); 61 | return this; 62 | } 63 | 64 | public Builder eventsSupported(final List events) { 65 | sc.superPut(EVENTS_SUPPORTED_MEMBER, events); 66 | return this; 67 | } 68 | 69 | public Builder eventsRequested(final List events) { 70 | sc.superPut(EVENTS_REQUESTED_MEMBER, events); 71 | return this; 72 | } 73 | 74 | public Builder eventsDelivered(final List events) { 75 | sc.superPut(EVENTS_DELIVERED_MEMBER, events); 76 | return this; 77 | } 78 | 79 | public Builder delivery(final JSONObject config) { 80 | sc.superPut(DELIVERY_MEMBER, config); 81 | return this; 82 | } 83 | 84 | public Builder minVerificationInterval(final int interval) { 85 | sc.superPut(MIN_VERIFICATION_INTERVAL_MEMBER, interval); 86 | return this; 87 | } 88 | 89 | public Builder format(final String format) { 90 | sc.superPut(FORMAT_MEMBER, format); 91 | return this; 92 | } 93 | 94 | public StreamConfiguration build() { 95 | return sc; 96 | } 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/StreamStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Commonly used SSE stream states for SSE/RISC/CAEP events. 14 | *

15 | * A Transmitter MAY decide to enable, pause or disable updates about a 16 | * SPAG independently of an update request from a Receiver. If a 17 | * Transmitter decides to start or stop events for a SPAG then the 18 | * Transmitter MUST do the following according to the status of the 19 | * stream. If the stream is: 20 | *

21 | * Enabled the Transmitter MUST send a SPAG stream updated 22 | * (Section 8.1.5) event respectively to the Receiver within the Event Stream. 23 | * Paused the Transmitter SHOULD send SPAG stream updated 24 | * (Section 8.1.5) after the Event Stream is re-started. 25 | * Disabled the Transmitter MAY send SPAG stream updated 26 | * (Section 8.1.5) after the Event Stream is re-enabled. 27 | */ 28 | public enum StreamStatus { 29 | 30 | ENABLED("enabled"), 31 | PAUSED("paused"), 32 | DISABLED("disabled"); 33 | 34 | private static final Map BY_NAME = new HashMap<>(); 35 | 36 | static { 37 | for (StreamStatus t : values()) { 38 | BY_NAME.put(t.name, t); 39 | } 40 | } 41 | 42 | private final String name; 43 | 44 | StreamStatus(final String s) { 45 | name = s; 46 | } 47 | 48 | public static StreamStatus valueOfLabel(final String name) { 49 | return BY_NAME.get(name); 50 | } 51 | 52 | public static boolean contains(final String name) { 53 | return BY_NAME.containsKey(name); 54 | } 55 | 56 | public boolean equalsName(final String otherName) { 57 | return name.equals(otherName); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return this.name; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/TransmitterConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | 11 | import java.util.List; 12 | 13 | public class TransmitterConfig extends JSONObject { 14 | 15 | public static class Builder { 16 | 17 | /** 18 | * REQUIRED. URL using the https scheme with no query or 19 | * fragment component that the Transmitter asserts as its Issuer 20 | * Identifier. This MUST be identical to the iss claim value in 21 | * Security Event Tokens issued from this Transmitter. 22 | */ 23 | private static final String ISSUER_MEMBER = "issuer"; 24 | 25 | 26 | /** 27 | * REQUIRED. URL of the Transmitter's JSON Web Key Set 28 | * [RFC7517] document. This contains the signing key(s) the Receiver 29 | * uses to validate signatures from the Transmitter. 30 | */ 31 | private static final String JWKS_URI_MEMBER = "jwks_uri"; 32 | 33 | /** 34 | * RECOMMENDED. List of supported delivery method URIs. 35 | */ 36 | private static final String DELIVERY_METHODS_SUPPORTED_MEMBER = "delivery_methods_supported"; 37 | 38 | /** 39 | * OPTIONAL. The URL of the Configuration Endpoint. 40 | */ 41 | private static final String CONFIGURATION_ENDPOINT_MEMBER = "configuration_endpoint"; 42 | 43 | 44 | /** 45 | * OPTIONAL. The URL of the Status Endpoint. 46 | */ 47 | private static final String STATUS_ENDPOINT_MEMBER = "status_endpoint"; 48 | 49 | /** 50 | * OPTIONAL. The URL of the Add Subject Endpoint. 51 | */ 52 | private static final String ADD_SUBJECT_ENDPOINT_MEMBER = "add_subject_endpoint"; 53 | 54 | 55 | /** 56 | * OPTIONAL. The URL of the Remove Subject Endpoint. 57 | */ 58 | private static final String REMOVE_SUBJECT_ENDPOINT_MEMBER = "remove_subject_endpoint"; 59 | 60 | /** 61 | * OPTIONAL. The URL of the Verification Endpoint. 62 | */ 63 | private static final String VERIFICATION_ENDPOINT_MEMBER = "verification_endpoint"; 64 | 65 | // List of Strings 66 | private static final String CRITICAL_SUBJECT_MEMBERS_MEMBER = "critical_subject_members"; 67 | 68 | /** 69 | * The members. 70 | */ 71 | private final TransmitterConfig members = new TransmitterConfig(); 72 | 73 | public Builder issuer(final String iss) { 74 | 75 | members.put(ISSUER_MEMBER, iss); 76 | return this; 77 | } 78 | 79 | public Builder jwksUri(final String jwks_uri) { 80 | 81 | members.put(JWKS_URI_MEMBER, jwks_uri); 82 | return this; 83 | } 84 | 85 | public Builder deliveryMethods(final List methods) { 86 | members.put(DELIVERY_METHODS_SUPPORTED_MEMBER, methods); 87 | return this; 88 | } 89 | 90 | public Builder configurationEndpoint(final String endpoint) { 91 | members.put(CONFIGURATION_ENDPOINT_MEMBER, endpoint); 92 | return this; 93 | } 94 | 95 | public Builder statusEndpoint(final String endpoint) { 96 | members.put(STATUS_ENDPOINT_MEMBER, endpoint); 97 | return this; 98 | } 99 | 100 | public Builder addSubjectEndpoint(final String endpoint) { 101 | members.put(ADD_SUBJECT_ENDPOINT_MEMBER, endpoint); 102 | return this; 103 | } 104 | 105 | public Builder removeSubjectEndpoint(final String endpoint) { 106 | members.put(REMOVE_SUBJECT_ENDPOINT_MEMBER, endpoint); 107 | return this; 108 | } 109 | 110 | public Builder verificationEndpoint(final String endpoint) { 111 | members.put(VERIFICATION_ENDPOINT_MEMBER, endpoint); 112 | return this; 113 | } 114 | 115 | public Builder criticalSubjectMembers(final List subjectMembers) { 116 | members.put(CRITICAL_SUBJECT_MEMBERS_MEMBER, subjectMembers); 117 | return this; 118 | } 119 | 120 | 121 | public TransmitterConfig build() { 122 | return members; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.Method; 13 | 14 | public class Utils { 15 | 16 | protected Utils() { 17 | } 18 | 19 | public static void validateMember(final SSEvent event, final String member, final Class memberCls) throws ValidationException { 20 | final SSEventTypes eventType = event.getEventType(); 21 | if (null == eventType) { 22 | /* Unknown event type, not instantiated via a normal constructor. */ 23 | return; 24 | } 25 | JSONObject members = (JSONObject) event.get(eventType.toString()); 26 | 27 | final Object o = members.get(member); 28 | if (null == o) { 29 | throw new ValidationException(event.getClass().getName() + " member " + member + " is missing or null."); 30 | } 31 | 32 | // Fun with reflection to invoke contains() if the enum.name has it (and throw an exception if it does not so we can go fix it 33 | Method methodToFind; 34 | try { 35 | methodToFind = memberCls.getMethod("contains", String.class); 36 | } catch (NoSuchMethodException | SecurityException e) { 37 | throw new ValidationException(memberCls.getName() + " does not have a contains() method."); 38 | } 39 | 40 | try { 41 | final Boolean present = (Boolean) methodToFind.invoke(memberCls, o.toString()); 42 | if (Boolean.FALSE.equals(present)) { 43 | throw new ValidationException(event.getClass().getName() + " member " + member + " has an invalid value."); 44 | } 45 | } catch (IllegalAccessException e) { 46 | throw new ValidationException(event.getClass().getName() + " member " + member + " IllegalAccessException: " + e); 47 | } catch (InvocationTargetException e) { 48 | throw new ValidationException(event.getClass().getName() + " member " + member + " InvocationTargetException: " + e); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/ValidationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | public class ValidationException extends Exception { 10 | public ValidationException(final String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPAssuranceLevelChange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.sse.model.SSEventTypes; 10 | import com.sailpoint.sse.model.Utils; 11 | import com.sailpoint.sse.model.ValidationException; 12 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 13 | 14 | import java.text.ParseException; 15 | 16 | public class CAEPAssuranceLevelChange extends CAEPBaseEvent { 17 | 18 | private static final String CURRENT_LEVEL = "current_level"; 19 | private static final String PREVIOUS_LEVEL = "previous_level"; 20 | private static final String CHANGE_DIRECTION = "change_direction"; 21 | 22 | @Override 23 | public void validate() throws ParseException, SIValidationException, ValidationException { 24 | super.validate(); 25 | Utils.validateMember(this, CURRENT_LEVEL, NISTAuthenticatorAssuranceLevel.class); 26 | Utils.validateMember(this, PREVIOUS_LEVEL, NISTAuthenticatorAssuranceLevel.class); 27 | Utils.validateMember(this, CHANGE_DIRECTION, CAEPAssuranceLevelChangeDirection.class); 28 | } 29 | 30 | public static class Builder extends CAEPBaseEvent.Builder { 31 | 32 | public Builder() { 33 | super(SSEventTypes.CAEP_ASSURANCE_LEVEL_CHANGE); 34 | } 35 | 36 | protected CAEPAssuranceLevelChange createObj() { 37 | return new CAEPAssuranceLevelChange(); 38 | } 39 | 40 | protected CAEPAssuranceLevelChange.Builder getThis() { 41 | return this; 42 | } 43 | 44 | public Builder currentLevel(final NISTAuthenticatorAssuranceLevel level) { 45 | members.put(CURRENT_LEVEL, level.toString()); 46 | return thisObj; 47 | } 48 | 49 | public Builder previousLevel(final NISTAuthenticatorAssuranceLevel level) { 50 | members.put(PREVIOUS_LEVEL, level.toString()); 51 | return thisObj; 52 | } 53 | 54 | public Builder changeDirection(final CAEPAssuranceLevelChangeDirection direction) { 55 | members.put(CHANGE_DIRECTION, direction.toString()); 56 | return thisObj; 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPAssuranceLevelChangeDirection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum CAEPAssuranceLevelChangeDirection { 13 | 14 | INCREASE("increase"), 15 | DECREASE("decrease"); 16 | 17 | private static final Map BY_NAME = new HashMap<>(); 18 | 19 | static { 20 | for (CAEPAssuranceLevelChangeDirection t : values()) { 21 | BY_NAME.put(t.name, t); 22 | } 23 | } 24 | 25 | private final String name; 26 | 27 | CAEPAssuranceLevelChangeDirection(final String s) { 28 | name = s; 29 | } 30 | 31 | public static CAEPAssuranceLevelChangeDirection valueOfLabel(final String name) { 32 | return BY_NAME.get(name); 33 | } 34 | 35 | @SuppressWarnings("unused") 36 | public static boolean contains(final String name) { 37 | return BY_NAME.containsKey(name); 38 | } 39 | 40 | public boolean equalsName(final String otherName) { 41 | return name.equals(otherName); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return this.name; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPBaseEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.sailpoint.sse.model.SSEvent; 11 | import com.sailpoint.sse.model.SSEventTypes; 12 | import com.sailpoint.sse.model.ValidationException; 13 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 14 | 15 | import java.text.ParseException; 16 | 17 | public abstract class CAEPBaseEvent extends SSEvent { 18 | private static final String EVENT_TIMESTAMP_MEMBER = "event_timestamp"; 19 | private static final String INITIATING_ENTITY_MEMBER = "initiating_entity"; 20 | private static final String REASON_ADMIN_MEMBER = "reason_admin"; 21 | private static final String REASON_USER_MEMBER = "reason_user"; 22 | 23 | protected CAEPBaseEvent() { } 24 | 25 | public JSONObject getEventMembers() throws ValidationException { 26 | final SSEventTypes eventType = getEventType(); 27 | if (null == eventType) { 28 | /* Unknown event type, not instantiated via a normal constructor. */ 29 | throw new ValidationException("CAEP Events must set eventType in their constructor"); 30 | } 31 | 32 | JSONObject members = (JSONObject) get(eventType.toString()); 33 | if (null == members) { 34 | throw new ValidationException("CAEP Events must have a container Map whose key is the event type URI"); 35 | } 36 | return members; 37 | } 38 | 39 | public void validateEventTimestamp() throws ValidationException { 40 | JSONObject members = getEventMembers(); 41 | 42 | Object eventTimestamp = members.get(EVENT_TIMESTAMP_MEMBER); 43 | if (null == eventTimestamp) { 44 | // event_timestamp is an optional member. Ignore it's absence. 45 | return; 46 | } 47 | 48 | if (!(eventTimestamp instanceof Long)) { 49 | throw new ValidationException("CAEP Events event_timestamp must be of type Long."); 50 | } 51 | Long eventTimestampL = (Long) eventTimestamp; 52 | if (eventTimestampL < 0) { 53 | throw new ValidationException("CAEP Events event_timestamp must be > 0."); 54 | } 55 | } 56 | 57 | @Override 58 | public void validate() throws ParseException, SIValidationException, ValidationException { 59 | super.validate(); 60 | validateEventTimestamp(); 61 | } 62 | 63 | public abstract static class Builder> 64 | extends SSEvent.Builder { 65 | 66 | protected Builder(final SSEventTypes eventType) { 67 | super(eventType); 68 | } 69 | 70 | // Timestamp in milliseconds from epoch 71 | public B eventTimestamp(final long eventTimestamp) { 72 | members.put(EVENT_TIMESTAMP_MEMBER, eventTimestamp); 73 | return thisObj; 74 | } 75 | 76 | public B initiatingEntity(final CAEPInitiatingEntity entity) { 77 | members.put(INITIATING_ENTITY_MEMBER, entity.toString()); 78 | return thisObj; 79 | } 80 | 81 | public B reasonAdmin(final String s) { 82 | members.put(REASON_ADMIN_MEMBER, s); 83 | return thisObj; 84 | } 85 | 86 | public B reasonUser(final String s) { 87 | members.put(REASON_USER_MEMBER, s); 88 | return thisObj; 89 | } 90 | 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPChangeType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum CAEPChangeType { 13 | 14 | CREATE("create"), 15 | REVOKE("revoke"), 16 | UPDATE("update"), 17 | DELETE("delete"); 18 | 19 | private static final Map BY_NAME = new HashMap<>(); 20 | 21 | static { 22 | for (CAEPChangeType t : values()) { 23 | BY_NAME.put(t.name, t); 24 | } 25 | } 26 | 27 | private final String name; 28 | 29 | CAEPChangeType(final String s) { 30 | name = s; 31 | } 32 | 33 | public static CAEPChangeType valueOfLabel(String name) { 34 | return BY_NAME.get(name); 35 | } 36 | 37 | public static boolean contains(final String name) { 38 | return BY_NAME.containsKey(name); 39 | } 40 | 41 | public boolean equalsName(String otherName) { 42 | return name.equals(otherName); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return this.name; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPComplianceStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum CAEPComplianceStatus { 13 | 14 | COMPLIANT("compliant"), 15 | NOT_COMPLIANT("not-compliant"); 16 | 17 | private static final Map BY_NAME = new HashMap<>(); 18 | 19 | static { 20 | for (CAEPComplianceStatus t : values()) { 21 | BY_NAME.put(t.name, t); 22 | } 23 | } 24 | 25 | private final String name; 26 | 27 | CAEPComplianceStatus(final String s) { 28 | name = s; 29 | } 30 | 31 | public static CAEPComplianceStatus valueOfLabel(final String name) { 32 | return BY_NAME.get(name); 33 | } 34 | 35 | public static boolean contains(final String name) { 36 | return BY_NAME.containsKey(name); 37 | } 38 | 39 | public boolean equalsName(final String otherName) { 40 | return name.equals(otherName); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return this.name; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPCredentialChange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | import com.sailpoint.sse.model.Utils; 12 | import com.sailpoint.sse.model.ValidationException; 13 | 14 | import java.text.ParseException; 15 | 16 | public class CAEPCredentialChange extends CAEPBaseEvent { 17 | 18 | private static final String CREDENTIAL_TYPE = "credential_type"; 19 | private static final String CHANGE_TYPE = "change_type"; 20 | private static final String FRIENDLY_NAME = "friendly_name"; 21 | private static final String X509_ISSUER = "x509_issuer"; 22 | private static final String X509_SERIAL = "x509_serial"; 23 | private static final String FIDO2_AAGUID = "fido2_aaguid"; 24 | 25 | @Override 26 | public void validate() throws ParseException, SIValidationException, ValidationException { 27 | super.validate(); 28 | Utils.validateMember(this, CREDENTIAL_TYPE, CAEPCredentialType.class); 29 | Utils.validateMember(this, CHANGE_TYPE, CAEPChangeType.class); 30 | } 31 | 32 | public static class Builder extends CAEPBaseEvent.Builder { 33 | 34 | 35 | public Builder() { 36 | super(SSEventTypes.CAEP_CREDENTIAL_CHANGE); 37 | } 38 | 39 | @Override 40 | protected CAEPCredentialChange createObj() { 41 | return new CAEPCredentialChange(); 42 | } 43 | 44 | protected CAEPCredentialChange.Builder getThis() { 45 | return this; 46 | } 47 | 48 | public Builder credentialType(final CAEPCredentialType type) { 49 | super.members.put(CREDENTIAL_TYPE, type.toString()); 50 | return thisObj; 51 | } 52 | 53 | public Builder changeType(final CAEPChangeType type) { 54 | super.members.put(CHANGE_TYPE, type.toString()); 55 | return thisObj; 56 | } 57 | 58 | public Builder friendlyName(final String name) { 59 | super.members.put(FRIENDLY_NAME, name); 60 | return thisObj; 61 | } 62 | 63 | public Builder x509Issuer(final String issuer) { 64 | super.members.put(X509_ISSUER, issuer); 65 | return thisObj; 66 | } 67 | 68 | public Builder x509Serial(final String serial) { 69 | super.members.put(X509_SERIAL, serial); 70 | return thisObj; 71 | } 72 | 73 | public Builder fido2AAGuid(final String guid) { 74 | super.members.put(FIDO2_AAGUID, guid); 75 | return thisObj; 76 | } 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPCredentialType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum CAEPCredentialType { 13 | 14 | PASSWORD("password"), 15 | PIN("pin"), 16 | X509("x509"), 17 | FIDO2_PLATFORM("fido2-platform"), 18 | FIDO2_ROAMING("fido2-roaming"), 19 | FIDO2_U2F("fido-u2f"), 20 | VERIFIABLE_CREDENTIAL("verifiable-credential"), 21 | PHONE_VOICE("phone-voice"), 22 | PHONE_SMS("phone-sms"), 23 | APP("app"); 24 | 25 | private static final Map BY_NAME = new HashMap<>(); 26 | 27 | static { 28 | for (CAEPCredentialType t : values()) { 29 | BY_NAME.put(t.name, t); 30 | } 31 | } 32 | 33 | private final String name; 34 | 35 | CAEPCredentialType(final String s) { 36 | name = s; 37 | } 38 | 39 | public static CAEPCredentialType valueOfLabel(final String name) { 40 | return BY_NAME.get(name); 41 | } 42 | 43 | public static boolean contains(final String name) { 44 | return BY_NAME.containsKey(name); 45 | } 46 | 47 | public boolean equalsName(final String otherName) { 48 | return name.equals(otherName); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return this.name; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPDeviceComplianceChange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | import com.sailpoint.sse.model.Utils; 12 | import com.sailpoint.sse.model.ValidationException; 13 | 14 | import java.text.ParseException; 15 | 16 | public class CAEPDeviceComplianceChange extends CAEPBaseEvent { 17 | 18 | private static final String PREVIOUS_STATUS = "previous_status"; 19 | private static final String CURRENT_STATUS = "current_status"; 20 | 21 | @Override 22 | public void validate() throws ParseException, SIValidationException, ValidationException { 23 | super.validate(); 24 | Utils.validateMember(this, PREVIOUS_STATUS, CAEPComplianceStatus.class); 25 | Utils.validateMember(this, CURRENT_STATUS, CAEPComplianceStatus.class); 26 | } 27 | 28 | public static class Builder extends CAEPBaseEvent.Builder { 29 | 30 | public Builder() { 31 | super(SSEventTypes.CAEP_DEVICE_COMPLIANCE_CHANGE); 32 | } 33 | 34 | protected CAEPDeviceComplianceChange createObj() { 35 | return new CAEPDeviceComplianceChange(); 36 | } 37 | 38 | protected CAEPDeviceComplianceChange.Builder getThis() { 39 | return this; 40 | } 41 | 42 | public Builder previousStatus(final CAEPComplianceStatus status) { 43 | members.put(PREVIOUS_STATUS, status.toString()); 44 | return thisObj; 45 | } 46 | 47 | public Builder currentStatus(final CAEPComplianceStatus status) { 48 | members.put(CURRENT_STATUS, status.toString()); 49 | return thisObj; 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPIPAddrChanged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.sse.model.SSEventTypes; 10 | 11 | public class CAEPIPAddrChanged extends CAEPBaseEvent { 12 | private static final String IPADDRESS_MEMBER = "ip_address"; 13 | 14 | public static class Builder extends CAEPBaseEvent.Builder { 15 | 16 | 17 | public Builder() { 18 | super(SSEventTypes.CAEP_IPADDR_CHANGED); 19 | } 20 | 21 | protected CAEPIPAddrChanged createObj() { 22 | return new CAEPIPAddrChanged(); 23 | } 24 | 25 | protected CAEPIPAddrChanged.Builder getThis() { 26 | return this; 27 | } 28 | 29 | public Builder ipAddress(final String ipAddress) { 30 | members.put(IPADDRESS_MEMBER, ipAddress); 31 | return thisObj; 32 | } 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPInitiatingEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum CAEPInitiatingEntity { 13 | ADMIN("admin"), 14 | USER("user"), 15 | POLICY("policy"), 16 | SYSTEM("system"); 17 | 18 | private static final Map BY_NAME = new HashMap<>(); 19 | 20 | static { 21 | for (CAEPInitiatingEntity t : values()) { 22 | BY_NAME.put(t.name, t); 23 | } 24 | } 25 | 26 | private final String name; 27 | 28 | CAEPInitiatingEntity(final String s) { 29 | name = s; 30 | } 31 | 32 | public static CAEPInitiatingEntity valueOfLabel(final String name) { 33 | return BY_NAME.get(name); 34 | } 35 | 36 | public static boolean contains(final String name) { 37 | return BY_NAME.containsKey(name); 38 | } 39 | 40 | public boolean equalsName(final String otherName) { 41 | return name.equals(otherName); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return this.name; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPSessionRevoked.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.sse.model.SSEventTypes; 10 | 11 | public class CAEPSessionRevoked extends CAEPBaseEvent { 12 | 13 | public static class Builder extends CAEPBaseEvent.Builder { 14 | 15 | public Builder() { 16 | super(SSEventTypes.CAEP_SESSION_REVOKED); 17 | } 18 | 19 | protected CAEPSessionRevoked createObj() { 20 | return new CAEPSessionRevoked(); 21 | } 22 | 23 | protected CAEPSessionRevoked.Builder getThis() { 24 | return this; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPStreamUpdated.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.sailpoint.sse.model.SSEventTypes; 10 | 11 | public class CAEPStreamUpdated extends CAEPBaseEvent { 12 | 13 | public static class Builder extends CAEPBaseEvent.Builder { 14 | 15 | public Builder() { 16 | super(SSEventTypes.SSE_STREAM_UPDATED); 17 | } 18 | 19 | protected CAEPStreamUpdated createObj() { 20 | return new CAEPStreamUpdated(); 21 | } 22 | 23 | protected CAEPStreamUpdated.Builder getThis() { 24 | return this; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/CAEPTokenClaimsChange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 11 | import com.sailpoint.sse.model.SSEventTypes; 12 | import com.sailpoint.sse.model.ValidationException; 13 | 14 | import java.text.ParseException; 15 | 16 | public class CAEPTokenClaimsChange extends CAEPBaseEvent { 17 | 18 | private static final String CLAIMS = "claims"; 19 | 20 | @Override 21 | public void validate() throws ParseException, SIValidationException, ValidationException { 22 | super.validate(); 23 | JSONObject members = getEventMembers(); 24 | final Object o = members.get(CLAIMS); 25 | if (null == o) { 26 | throw new ValidationException(this.getClass().getName() + " member " + CLAIMS + " is missing or null."); 27 | } 28 | } 29 | 30 | public static class Builder extends CAEPBaseEvent.Builder { 31 | 32 | public Builder() { 33 | super(SSEventTypes.CAEP_TOKEN_CLAIMS_CHANGE); 34 | } 35 | 36 | protected CAEPTokenClaimsChange createObj() { 37 | return new CAEPTokenClaimsChange(); 38 | } 39 | 40 | protected CAEPTokenClaimsChange.Builder getThis() { 41 | return this; 42 | } 43 | 44 | public CAEPTokenClaimsChange.Builder claims(final JSONObject newClaims) { 45 | members.put(CLAIMS, newClaims); 46 | return thisObj; 47 | } 48 | 49 | 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/caep/NISTAuthenticatorAssuranceLevel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.caep; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum NISTAuthenticatorAssuranceLevel { 13 | 14 | NIST_AAL1("nist-aal1"), 15 | NIST_AAL2("nist-aal2"), 16 | NIST_AAL3("nist-aal3"); 17 | 18 | private static final Map BY_NAME = new HashMap<>(); 19 | 20 | static { 21 | for (NISTAuthenticatorAssuranceLevel t : values()) { 22 | BY_NAME.put(t.name, t); 23 | } 24 | } 25 | 26 | private final String name; 27 | 28 | NISTAuthenticatorAssuranceLevel(final String s) { 29 | name = s; 30 | } 31 | 32 | public static NISTAuthenticatorAssuranceLevel valueOfLabel(final String name) { 33 | return BY_NAME.get(name); 34 | } 35 | 36 | public static boolean contains(final String name) { 37 | return BY_NAME.containsKey(name); 38 | } 39 | 40 | public boolean equalsName(final String otherName) { 41 | return name.equals(otherName); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return this.name; 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCAccountCredentialChangeRequired.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCAccountCredentialChangeRequired extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_ACCOUNT_CREDENTIAL_CHANGE_REQUIRED); 18 | } 19 | 20 | @Override 21 | protected RISCAccountCredentialChangeRequired createObj() { 22 | return new RISCAccountCredentialChangeRequired(); 23 | } 24 | 25 | protected RISCAccountCredentialChangeRequired.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCAccountDisabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | import com.sailpoint.sse.model.SSEvent; 11 | import com.sailpoint.sse.model.SSEventTypes; 12 | import com.sailpoint.sse.model.Utils; 13 | import com.sailpoint.sse.model.ValidationException; 14 | 15 | import java.text.ParseException; 16 | 17 | public class RISCAccountDisabled extends SSEvent { 18 | 19 | private static final String REASON_MEMBER = "reason"; 20 | 21 | @Override 22 | public void validate() throws ParseException, SIValidationException, ValidationException { 23 | super.validate(); 24 | Utils.validateMember(this, REASON_MEMBER, RISCAccountDisabledReasons.class); 25 | } 26 | 27 | public static class Builder extends SSEvent.Builder { 28 | 29 | public Builder() { 30 | super(SSEventTypes.RISC_ACCOUNT_DISABLED); 31 | } 32 | 33 | @Override 34 | protected RISCAccountDisabled createObj() { 35 | return new RISCAccountDisabled(); 36 | } 37 | 38 | protected RISCAccountDisabled.Builder getThis() { 39 | return this; 40 | } 41 | 42 | public RISCAccountDisabled.Builder reason(final RISCAccountDisabledReasons reason) { 43 | members.put(REASON_MEMBER, reason.toString()); 44 | return thisObj; 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCAccountDisabledReasons.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public enum RISCAccountDisabledReasons { 13 | 14 | HIJACKING("hijacking"), 15 | BULK_ACCOUNT("bulk-account"); 16 | 17 | private static final Map BY_NAME = new HashMap<>(); 18 | 19 | static { 20 | for (RISCAccountDisabledReasons t : values()) { 21 | BY_NAME.put(t.name, t); 22 | } 23 | } 24 | 25 | private final String name; 26 | 27 | RISCAccountDisabledReasons(final String s) { 28 | name = s; 29 | } 30 | 31 | public static RISCAccountDisabledReasons valueOfLabel(final String name) { 32 | return BY_NAME.get(name); 33 | } 34 | 35 | public static boolean contains(final String name) { 36 | return BY_NAME.containsKey(name); 37 | } 38 | 39 | public boolean equalsName(final String otherName) { 40 | return name.equals(otherName); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return this.name; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCAccountEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCAccountEnabled extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_ACCOUNT_ENABLED); 18 | } 19 | 20 | @Override 21 | protected RISCAccountEnabled createObj() { 22 | return new RISCAccountEnabled(); 23 | } 24 | 25 | protected RISCAccountEnabled.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCAccountPurged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCAccountPurged extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_ACCOUNT_PURGED); 18 | } 19 | 20 | @Override 21 | protected RISCAccountPurged createObj() { 22 | return new RISCAccountPurged(); 23 | } 24 | 25 | protected RISCAccountPurged.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCIdentifierChanged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | import com.sailpoint.sse.model.SSEvent; 11 | import com.sailpoint.sse.model.SSEventTypes; 12 | import com.sailpoint.sse.model.ValidationException; 13 | 14 | import java.text.ParseException; 15 | 16 | public class RISCIdentifierChanged extends SSEvent { 17 | 18 | private static final String NEW_VALUE_MEMBER = "new-value"; 19 | 20 | @Override 21 | public void validate() throws ParseException, SIValidationException, ValidationException { 22 | super.validate(); 23 | Object oNewValue = getMember(NEW_VALUE_MEMBER); 24 | if (null == oNewValue) { 25 | throw new ValidationException(String.format("RISC Identifier Changed event member %s must be present.", NEW_VALUE_MEMBER)); 26 | } 27 | if (!(oNewValue instanceof String)) { 28 | throw new ValidationException(String.format("RISC Identifier Changed event member %s must be a String", NEW_VALUE_MEMBER)); 29 | } 30 | } 31 | 32 | public static class Builder extends SSEvent.Builder { 33 | 34 | public Builder() { 35 | super(SSEventTypes.RISC_IDENTIFIER_CHANGED); 36 | } 37 | 38 | @Override 39 | protected RISCIdentifierChanged createObj() { 40 | return new RISCIdentifierChanged(); 41 | } 42 | 43 | protected RISCIdentifierChanged.Builder getThis() { 44 | return this; 45 | } 46 | 47 | public RISCIdentifierChanged.Builder newValue(final String value) { 48 | members.put(NEW_VALUE_MEMBER, value); 49 | return thisObj; 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCIdentifierRecycled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 10 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 11 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifierFormats; 12 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifierMembers; 13 | import com.sailpoint.sse.model.SSEvent; 14 | import com.sailpoint.sse.model.SSEventTypes; 15 | import com.sailpoint.sse.model.ValidationException; 16 | 17 | import java.text.ParseException; 18 | 19 | public class RISCIdentifierRecycled extends SSEvent { 20 | 21 | /* 22 | * The subject type MUST be either "email" or "phone". 23 | */ 24 | @Override 25 | public void validate() throws ParseException, SIValidationException, ValidationException { 26 | super.validate(); 27 | SubjectIdentifier subj = getSubjectIdentifier(); 28 | final String type = subj.getString(SubjectIdentifierMembers.FORMAT); 29 | if (SubjectIdentifierFormats.EMAIL.equalsName(type) || SubjectIdentifierFormats.PHONE_NUMBER.equalsName(type)) { 30 | return; 31 | } 32 | throw new ValidationException("RISC Identifier Recycled event must have a subject_type of email or phone_number"); 33 | } 34 | 35 | public static class Builder extends SSEvent.Builder { 36 | 37 | public Builder() { 38 | super(SSEventTypes.RISC_IDENTIFIER_RECYCLED); 39 | } 40 | 41 | @Override 42 | protected RISCIdentifierRecycled createObj() { 43 | return new RISCIdentifierRecycled(); 44 | } 45 | 46 | protected RISCIdentifierRecycled.Builder getThis() { 47 | return this; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCOptIn.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCOptIn extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_OPT_IN); 18 | } 19 | 20 | @Override 21 | protected RISCOptIn createObj() { 22 | return new RISCOptIn(); 23 | } 24 | 25 | protected RISCOptIn.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCOptOutCancelled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCOptOutCancelled extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_OPT_OUT_CANCELLED); 18 | } 19 | 20 | @Override 21 | protected RISCOptOutCancelled createObj() { 22 | return new RISCOptOutCancelled(); 23 | } 24 | 25 | protected RISCOptOutCancelled.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCOptOutEffective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCOptOutEffective extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_OPT_OUT_EFFECTIVE); 18 | } 19 | 20 | @Override 21 | protected RISCOptOutEffective createObj() { 22 | return new RISCOptOutEffective(); 23 | } 24 | 25 | protected RISCOptOutEffective.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCOptOutInitiated.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCOptOutInitiated extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_OPT_OUT_INITIATED); 18 | } 19 | 20 | @Override 21 | protected RISCOptOutInitiated createObj() { 22 | return new RISCOptOutInitiated(); 23 | } 24 | 25 | protected RISCOptOutInitiated.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCRecoveryActivated.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCRecoveryActivated extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_RECOVERY_ACTIVATED); 18 | } 19 | 20 | @Override 21 | protected RISCRecoveryActivated createObj() { 22 | return new RISCRecoveryActivated(); 23 | } 24 | 25 | protected RISCRecoveryActivated.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCRecoveryInformationChanged.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCRecoveryInformationChanged extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_RECOVERY_INFORMATION_CHANGED); 18 | } 19 | 20 | @Override 21 | protected RISCRecoveryInformationChanged createObj() { 22 | return new RISCRecoveryInformationChanged(); 23 | } 24 | 25 | protected RISCRecoveryInformationChanged.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/risc/RISCSessionsRevoked.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.risc; 8 | 9 | import com.sailpoint.sse.model.SSEvent; 10 | import com.sailpoint.sse.model.SSEventTypes; 11 | 12 | public class RISCSessionsRevoked extends SSEvent { 13 | 14 | public static class Builder extends SSEvent.Builder { 15 | 16 | public Builder() { 17 | super(SSEventTypes.RISC_SESSIONS_REVOKED); 18 | } 19 | 20 | @Override 21 | protected RISCSessionsRevoked createObj() { 22 | return new RISCSessionsRevoked(); 23 | } 24 | 25 | protected RISCSessionsRevoked.Builder getThis() { 26 | return this; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/sse/SSEStreamUpdated.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.sse; 8 | 9 | import com.sailpoint.sse.model.*; 10 | 11 | public class SSEStreamUpdated extends SSEvent { 12 | 13 | private static final String STATUS_MEMBER = "status"; 14 | 15 | public static class Builder extends SSEvent.Builder { 16 | 17 | public Builder() { 18 | super(SSEventTypes.SSE_STREAM_UPDATED); 19 | } 20 | 21 | @Override 22 | protected SSEStreamUpdated createObj() { 23 | return new SSEStreamUpdated(); 24 | } 25 | 26 | protected SSEStreamUpdated.Builder getThis() { 27 | return this; 28 | } 29 | 30 | public Builder status(final StreamStatus status) { 31 | members.put(STATUS_MEMBER, status.toString()); 32 | return thisObj; 33 | } 34 | 35 | } 36 | 37 | @Override 38 | public void validate() throws ValidationException { 39 | super.validateEventTypeName(); 40 | Utils.validateMember(this, STATUS_MEMBER, StreamStatus.class); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/sailpoint/sse/model/sse/SSEVerification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model.sse; 8 | 9 | import com.sailpoint.sse.model.*; 10 | 11 | public class SSEVerification extends SSEvent { 12 | 13 | private static final String STATE_MEMBER = "state"; 14 | 15 | @Override 16 | public void validate() throws ValidationException { 17 | super.validateEventTypeName(); 18 | } 19 | 20 | 21 | 22 | public static class Builder extends SSEvent.Builder { 23 | 24 | public Builder() { 25 | super(SSEventTypes.SSE_VERIFICATION); 26 | } 27 | 28 | @Override 29 | protected SSEVerification createObj() { 30 | return new SSEVerification(); 31 | } 32 | 33 | protected SSEVerification.Builder getThis() { 34 | return this; 35 | } 36 | 37 | public Builder state(final String state) { 38 | super.members.put(STATE_MEMBER, state); 39 | return thisObj; 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | module com.sailpoint.sse.model { 8 | exports com.sailpoint.sse.model; 9 | exports com.sailpoint.sse.model.caep; 10 | exports com.sailpoint.sse.model.risc; 11 | exports com.sailpoint.sse.model.sse; 12 | requires transitive com.nimbusds.jose.jwt; 13 | requires com.sailpoint.ietf.subjectidentifiers.model; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/CAEPAssuranceLevelChangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.caep.CAEPAssuranceLevelChange; 14 | import com.sailpoint.sse.model.caep.CAEPAssuranceLevelChangeDirection; 15 | import com.sailpoint.sse.model.caep.CAEPInitiatingEntity; 16 | import com.sailpoint.sse.model.caep.NISTAuthenticatorAssuranceLevel; 17 | import com.sailpoint.ietf.subjectidentifiers.model.IssSubSubjectIdentifier; 18 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 19 | import org.junit.Test; 20 | 21 | import java.text.ParseException; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | 25 | public class CAEPAssuranceLevelChangeTest { 26 | /** 27 | * Figure 9: Example: Assurance Level Increase - Simple Subject + 28 | * optional claims 29 | */ 30 | 31 | @Test 32 | public void Figure9() throws ParseException, SIValidationException, ValidationException { 33 | IssSubSubjectIdentifier subj = new IssSubSubjectIdentifier.Builder() 34 | .issuer("https://idp.example.com/3456789/") 35 | .subject("jane.smith@example.com") 36 | .build(); 37 | 38 | CAEPAssuranceLevelChange evt = new CAEPAssuranceLevelChange.Builder() 39 | .currentLevel(NISTAuthenticatorAssuranceLevel.NIST_AAL2) 40 | .previousLevel(NISTAuthenticatorAssuranceLevel.NIST_AAL1) 41 | .changeDirection(CAEPAssuranceLevelChangeDirection.INCREASE) 42 | .eventTimestamp(1615304991643L) 43 | .initiatingEntity(CAEPInitiatingEntity.USER) 44 | .subject(subj) 45 | .build(); 46 | 47 | JWTClaimsSet set = new JWTClaimsSet.Builder() 48 | .issuer("https://idp.example.com/3456789/") 49 | .jwtID("07efd930f0977e4fcc1149a733ce7f78") 50 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 51 | .audience("https://sp.example2.net/caep") 52 | .claim(SEToken.EVENTS_CLAIM, evt) 53 | .build(); 54 | 55 | final String figure_text = " {\n" + 56 | " \"iss\": \"https://idp.example.com/3456789/\",\n" + 57 | " \"jti\": \"07efd930f0977e4fcc1149a733ce7f78\",\n" + 58 | " \"iat\": 1615305159,\n" + 59 | " \"aud\": \"https://sp.example2.net/caep\",\n" + 60 | " \"events\": {\n" + 61 | " \"https://schemas.openid.net/secevent/caep/event-type/assurance-level-change\": {\n" + 62 | " \"subject\": {\n" + 63 | " \"format\": \"iss_sub\",\n" + 64 | " \"iss\": \"https://idp.example.com/3456789/\",\n" + 65 | " \"sub\": \"jane.smith@example.com\"\n" + 66 | " },\n" + 67 | " \"current_level\": \"nist-aal2\",\n" + 68 | " \"previous_level\": \"nist-aal1\",\n" + 69 | " \"change_direction\": \"increase\",\n" + 70 | " \"initiating_entity\": \"user\",\n" + 71 | " \"event_timestamp\": 1615304991643\n" + 72 | " }\n" + 73 | " }\n" + 74 | " }\n"; 75 | 76 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 77 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 78 | assertEquals(figureJson, setJson); 79 | evt.validate(); 80 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 81 | } 82 | } -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/CAEPCredentialChangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.caep.CAEPChangeType; 14 | import com.sailpoint.sse.model.caep.CAEPCredentialChange; 15 | import com.sailpoint.sse.model.caep.CAEPCredentialType; 16 | import com.sailpoint.sse.model.caep.CAEPInitiatingEntity; 17 | import com.sailpoint.ietf.subjectidentifiers.model.IssSubSubjectIdentifier; 18 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 19 | import org.junit.Test; 20 | 21 | import java.text.ParseException; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | 25 | public class CAEPCredentialChangeTest { 26 | /** 27 | * Figure 8: Example: Provisioning a new FIDO2 authenticator - Simple 28 | * Subject + optional claims 29 | */ 30 | 31 | @Test 32 | public void Figure8() throws ParseException, SIValidationException, ValidationException { 33 | IssSubSubjectIdentifier subj = new IssSubSubjectIdentifier.Builder() 34 | .issuer("https://idp.example.com/3456789/") 35 | .subject("jane.smith@example.com") 36 | .build(); 37 | 38 | CAEPCredentialChange evt = new CAEPCredentialChange.Builder() 39 | .credentialType(CAEPCredentialType.FIDO2_ROAMING) 40 | .changeType(CAEPChangeType.CREATE) 41 | .fido2AAGuid("accced6a-63f5-490a-9eea-e59bc1896cfc") 42 | .eventTimestamp(1615304991643L) 43 | .initiatingEntity(CAEPInitiatingEntity.USER) 44 | .subject(subj) 45 | .reasonAdmin("User self-enrollment") 46 | .friendlyName("Jane's USB authenticator") 47 | .build(); 48 | 49 | JWTClaimsSet set = new JWTClaimsSet.Builder() 50 | .issuer("https://idp.example.com/3456789/") 51 | .jwtID("07efd930f0977e4fcc1149a733ce7f78") 52 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 53 | .audience("https://sp.example2.net/caep") 54 | .claim(SEToken.EVENTS_CLAIM, evt) 55 | .build(); 56 | 57 | final String figure_text = " {\n" + 58 | " \"iss\": \"https://idp.example.com/3456789/\",\n" + 59 | " \"jti\": \"07efd930f0977e4fcc1149a733ce7f78\",\n" + 60 | " \"iat\": 1615305159,\n" + 61 | " \"aud\": \"https://sp.example2.net/caep\",\n" + 62 | " \"events\": {\n" + 63 | " \"https://schemas.openid.net/secevent/caep/event-type/credential-change\": {\n" + 64 | " \"subject\": {\n" + 65 | " \"format\": \"iss_sub\",\n" + 66 | " \"iss\": \"https://idp.example.com/3456789/\",\n" + 67 | " \"sub\": \"jane.smith@example.com\"\n" + 68 | " },\n" + 69 | " \"credential_type\": \"fido2-roaming\",\n" + 70 | " \"change_type\": \"create\",\n" + 71 | " \"fido2_aaguid\": \"accced6a-63f5-490a-9eea-e59bc1896cfc\",\n" + 72 | " \"friendly_name\": \"Jane's USB authenticator\",\n" + 73 | " \"initiating_entity\": \"user\",\n" + 74 | " \"reason_admin\": \"User self-enrollment\",\n" + 75 | " \"event_timestamp\": 1615304991643\n" + 76 | " }\n" + 77 | " }\n" + 78 | " }"; 79 | 80 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 81 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 82 | assertEquals(figureJson, setJson); 83 | evt.validate(); 84 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/CAEPDeviceComplianceChangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.ietf.subjectidentifiers.model.IssSubSubjectIdentifier; 14 | import com.sailpoint.ietf.subjectidentifiers.model.OpaqueSubjectIdentifier; 15 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 16 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 17 | import com.sailpoint.sse.model.caep.CAEPComplianceStatus; 18 | import com.sailpoint.sse.model.caep.CAEPDeviceComplianceChange; 19 | import com.sailpoint.sse.model.caep.CAEPInitiatingEntity; 20 | import org.junit.Test; 21 | 22 | import java.text.ParseException; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class CAEPDeviceComplianceChangeTest { 27 | /** 28 | * Figure 10: Example: Device No Longer Compliant - Complex Subject + 29 | * optional claims 30 | */ 31 | 32 | @Test 33 | public void Figure10() throws ParseException, SIValidationException, ValidationException { 34 | IssSubSubjectIdentifier device = new IssSubSubjectIdentifier.Builder() 35 | .issuer("https://idp.example.com/123456789/") 36 | .subject("e9297990-14d2-42ec-a4a9-4036db86509a") 37 | .build(); 38 | 39 | OpaqueSubjectIdentifier tenant = new OpaqueSubjectIdentifier.Builder() 40 | .id("123456789") 41 | .build(); 42 | 43 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 44 | .device(device) 45 | .tenant(tenant) 46 | .build(); 47 | 48 | CAEPDeviceComplianceChange evt = new CAEPDeviceComplianceChange.Builder() 49 | .previousStatus(CAEPComplianceStatus.COMPLIANT) 50 | .currentStatus(CAEPComplianceStatus.NOT_COMPLIANT) 51 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 52 | .reasonAdmin("Location Policy Violation: C076E82F") 53 | .reasonUser("Device is no longer in a trusted location.") 54 | .eventTimestamp(1615304991643L) 55 | .subject(subj) 56 | .build(); 57 | 58 | JWTClaimsSet set = new JWTClaimsSet.Builder() 59 | .issuer("https://idp.example.com/123456789/") 60 | .jwtID("24c63fb56e5a2d77a6b512616ca9fa24") 61 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 62 | .audience("https://sp.example.com/caep") 63 | .claim(SEToken.EVENTS_CLAIM, evt) 64 | .build(); 65 | 66 | final String figure_text = "{\n" + 67 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 68 | " \"jti\": \"24c63fb56e5a2d77a6b512616ca9fa24\",\n" + 69 | " \"iat\": 1615305159,\n" + 70 | " \"aud\": \"https://sp.example.com/caep\",\n" + 71 | " \"events\": {\n" + 72 | " \"https://schemas.openid.net/secevent/caep/event-type/device-compliance-change\": {\n" + 73 | " \"subject\": {\n" + 74 | " \"device\": {\n" + 75 | " \"format\": \"iss_sub\",\n" + 76 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 77 | " \"sub\": \"e9297990-14d2-42ec-a4a9-4036db86509a\"\n" + 78 | " },\n" + 79 | " \"tenant\": {\n" + 80 | " \"format\": \"opaque\",\n" + 81 | " \"id\": \"123456789\"\n" + 82 | " }\n" + 83 | " },\n" + 84 | " \"current_status\": \"not-compliant\",\n" + 85 | " \"previous_status\": \"compliant\",\n" + 86 | " \"initiating_entity\": \"policy\",\n" + 87 | " \"reason_admin\": \"Location Policy Violation: C076E82F\",\n" + 88 | " \"reason_user\": \"Device is no longer in a trusted location.\",\n" + 89 | " \"event_timestamp\": 1615304991643\n" + 90 | " }\n" + 91 | " }\n" + 92 | "}\n"; 93 | 94 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 95 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 96 | assertEquals(figureJson, setJson); 97 | evt.validate(); 98 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 99 | } 100 | } -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/CAEPSessionRevokedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.caep.CAEPInitiatingEntity; 14 | import com.sailpoint.sse.model.caep.CAEPSessionRevoked; 15 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 16 | import com.sailpoint.ietf.subjectidentifiers.model.OpaqueSubjectIdentifier; 17 | import com.sailpoint.ietf.subjectidentifiers.model.IssSubSubjectIdentifier; 18 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifierFormats; 19 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 20 | import org.junit.Test; 21 | 22 | import java.text.ParseException; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class CAEPSessionRevokedTest { 27 | /** 28 | * Figure 1: Example: Session Revoked - Required claims + Simple Subject 29 | */ 30 | @Test 31 | public void Figure1() throws ParseException, SIValidationException, ValidationException { 32 | OpaqueSubjectIdentifier subj = new OpaqueSubjectIdentifier.Builder() 33 | .id("dMTlD|1600802906337.16|16008.16") 34 | .build(); 35 | 36 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 37 | .eventTimestamp(1615304991643L) 38 | .subject(subj) 39 | .build(); 40 | 41 | JWTClaimsSet set = new JWTClaimsSet.Builder() 42 | .issuer("https://idp.example.com/123456789/") 43 | .jwtID("24c63fb56e5a2d77a6b512616ca9fa24") 44 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 45 | .audience("https://sp.example.com/caep") 46 | .claim(SEToken.EVENTS_CLAIM, evt) 47 | .build(); 48 | 49 | final String figure_text = " {\n" + 50 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 51 | " \"jti\": \"24c63fb56e5a2d77a6b512616ca9fa24\",\n" + 52 | " \"iat\": 1615305159,\n" + 53 | " \"aud\": \"https://sp.example.com/caep\",\n" + 54 | " \"events\": {\n" + 55 | " \"https://schemas.openid.net/secevent/caep/event-type/session-revoked\": {\n" + 56 | " \"subject\": {\n" + 57 | " \"format\": \"opaque\",\n" + 58 | " \"id\": \"dMTlD|1600802906337.16|16008.16\"\n" + 59 | " },\n" + 60 | " \"event_timestamp\": 1615304991643\n" + 61 | " }\n" + 62 | " }\n" + 63 | " }\n"; 64 | 65 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 66 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 67 | assertEquals(figureJson, setJson); 68 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 69 | } 70 | 71 | 72 | /** 73 | * Figure 2: Example: Session Revoked - Complex Subject describing user 74 | * + session ID + device (includes optional claims) 75 | */ 76 | 77 | @Test 78 | public void Figure2() throws ParseException, SIValidationException, ValidationException { 79 | OpaqueSubjectIdentifier session = new OpaqueSubjectIdentifier.Builder() 80 | .id("dMTlD|1600802906337.16|16008.16") 81 | .build(); 82 | 83 | IssSubSubjectIdentifier user = new IssSubSubjectIdentifier.Builder() 84 | .issuer("https://idp.example.com/123456789/") 85 | .subject("dMTlD|1600802906337.16|16008.16") 86 | .build(); 87 | 88 | OpaqueSubjectIdentifier tenant = new OpaqueSubjectIdentifier.Builder() 89 | .id("123456789") 90 | .build(); 91 | 92 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 93 | .session(session) 94 | .user(user) 95 | .tenant(tenant) 96 | .build(); 97 | 98 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 99 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 100 | .reasonAdmin("Landspeed Policy Violation: C076E82F") 101 | .reasonUser("Access attempt from multiple regions.") 102 | .eventTimestamp(1615304991643L) 103 | .subject(subj) 104 | .build(); 105 | 106 | JWTClaimsSet set = new JWTClaimsSet.Builder() 107 | .issuer("https://idp.example.com/123456789/") 108 | .jwtID("24c63fb56e5a2d77a6b512616ca9fa24") 109 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 110 | .audience("https://sp.example.com/caep") 111 | .claim(SEToken.EVENTS_CLAIM, evt) 112 | .build(); 113 | 114 | final String figure_text = " {\n" + 115 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 116 | " \"jti\": \"24c63fb56e5a2d77a6b512616ca9fa24\",\n" + 117 | " \"iat\": 1615305159,\n" + 118 | " \"aud\": \"https://sp.example.com/caep\",\n" + 119 | " \"events\": {\n" + 120 | " \"https://schemas.openid.net/secevent/caep/event-type/session-revoked\": {\n" + 121 | " \"subject\": {\n" + 122 | " \"session\": {\n" + 123 | " \"format\": \"opaque\",\n" + 124 | " \"id\": \"dMTlD|1600802906337.16|16008.16\"\n" + 125 | " },\n" + 126 | " \"user\": {\n" + 127 | " \"format\": \"iss_sub\",\n" + 128 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 129 | " \"sub\": \"dMTlD|1600802906337.16|16008.16\"\n" + 130 | " },\n" + 131 | " \"tenant\": {\n" + 132 | " \"format\": \"opaque\",\n" + 133 | " \"id\": \"123456789\"\n" + 134 | " }\n" + 135 | " },\n" + 136 | " \"initiating_entity\": \"policy\",\n" + 137 | " \"reason_admin\": \"Landspeed Policy Violation: C076E82F\",\n" + 138 | " \"reason_user\": \"Access attempt from multiple regions.\",\n" + 139 | " \"event_timestamp\": 1615304991643\n" + 140 | " }\n" + 141 | " }\n" + 142 | " }\n"; 143 | 144 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 145 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 146 | assertEquals(figureJson, setJson); 147 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 148 | } 149 | 150 | /** 151 | * Figure 3: Example: Session Revoked - subject as `sub` claim (includes 152 | * optional claims) 153 | */ 154 | 155 | @Test 156 | public void Figure3() throws ParseException, SIValidationException, ValidationException { 157 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 158 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 159 | .reasonAdmin("Landspeed Policy Violation: C076E82F") 160 | .reasonUser("Access attempt from multiple regions.") 161 | .eventTimestamp(1615304991643L) 162 | .build(); 163 | 164 | JWTClaimsSet set = new JWTClaimsSet.Builder() 165 | .issuer("https://idp.example.com/123456789/") 166 | .jwtID("24c63fb56e5a2d77a6b512616ca9fa24") 167 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 168 | .audience("https://sp.example.com/caep") 169 | .subject("jane.smith@example.com") 170 | .claim(SEToken.EVENTS_CLAIM, evt) 171 | .build(); 172 | 173 | final String figure_text = " {\n" + 174 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 175 | " \"jti\": \"24c63fb56e5a2d77a6b512616ca9fa24\",\n" + 176 | " \"iat\": 1615305159,\n" + 177 | " \"aud\": \"https://sp.example.com/caep\",\n" + 178 | " \"sub\": \"jane.smith@example.com\",\n" + 179 | " \"events\": {\n" + 180 | " \"https://schemas.openid.net/secevent/caep/event-type/session-revoked\": {\n" + 181 | " \"initiating_entity\": \"policy\",\n" + 182 | " \"reason_admin\": \"Landspeed Policy Violation: C076E82F\",\n" + 183 | " \"reason_user\": \"Access attempt from multiple regions.\",\n" + 184 | " \"event_timestamp\": 1615304991643\n" + 185 | " }\n" + 186 | " }\n" + 187 | " }"; 188 | 189 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 190 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 191 | assertEquals(figureJson, setJson); 192 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 193 | } 194 | 195 | /** 196 | * Figure 4: Example: Session Revoked - Complex Subject describing user 197 | * + device + tenant (includes optional claims) 198 | */ 199 | 200 | @Test 201 | public void Figure4() throws ParseException, SIValidationException, ValidationException { 202 | IssSubSubjectIdentifier user = new IssSubSubjectIdentifier.Builder() 203 | .issuer("https://idp.example.com/123456789/") 204 | .subject("jane.smith@example.com") 205 | .build(); 206 | 207 | IssSubSubjectIdentifier device = new IssSubSubjectIdentifier.Builder() 208 | .issuer("https://idp.example.com/123456789/") 209 | .subject("e9297990-14d2-42ec-a4a9-4036db86509a") 210 | .build(); 211 | 212 | OpaqueSubjectIdentifier tenant = new OpaqueSubjectIdentifier.Builder() 213 | .id("123456789") 214 | .build(); 215 | 216 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 217 | .user(user) 218 | .device(device) 219 | .tenant(tenant) 220 | .build(); 221 | 222 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 223 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 224 | .reasonAdmin("Policy Violation: C076E82F") 225 | .reasonUser("This device is no longer compliant.") 226 | .eventTimestamp(1615304991643L) 227 | .subject(subj) 228 | .build(); 229 | 230 | JWTClaimsSet set = new JWTClaimsSet.Builder() 231 | .issuer("https://idp.example.com/123456789/") 232 | .jwtID("24c63fb56e5a2d77a6b512616ca9fa24") 233 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 234 | .audience("https://sp.example.com/caep") 235 | .claim(SEToken.EVENTS_CLAIM, evt) 236 | .build(); 237 | 238 | final String figure_text = " {\n" + 239 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 240 | " \"jti\": \"24c63fb56e5a2d77a6b512616ca9fa24\",\n" + 241 | " \"iat\": 1615305159,\n" + 242 | " \"aud\": \"https://sp.example.com/caep\",\n" + 243 | " \"events\": {\n" + 244 | " \"https://schemas.openid.net/secevent/caep/event-type/session-revoked\": {\n" + 245 | " \"subject\": {\n" + 246 | " \"user\": {\n" + 247 | " \"format\": \"iss_sub\",\n" + 248 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 249 | " \"sub\": \"jane.smith@example.com\"\n" + 250 | " },\n" + 251 | " \"device\": {\n" + 252 | " \"format\": \"iss_sub\",\n" + 253 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 254 | " \"sub\": \"e9297990-14d2-42ec-a4a9-4036db86509a\"\n" + 255 | " },\n" + 256 | " \"tenant\": {\n" + 257 | " \"format\": \"opaque\",\n" + 258 | " \"id\": \"123456789\"\n" + 259 | " }\n" + 260 | " },\n" + 261 | " \"initiating_entity\": \"policy\",\n" + 262 | " \"reason_admin\": \"Policy Violation: C076E82F\",\n" + 263 | " \"reason_user\": \"This device is no longer compliant.\",\n" + 264 | " \"event_timestamp\": 1615304991643\n" + 265 | " }\n" + 266 | " }\n" + 267 | " }\n"; 268 | 269 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 270 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 271 | assertEquals(figureJson, setJson); 272 | 273 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 274 | } 275 | 276 | } -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/CAEPTokenClaimsChangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.caep.CAEPInitiatingEntity; 14 | import com.sailpoint.sse.model.caep.CAEPTokenClaimsChange; 15 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 16 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifierFormats; 17 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 18 | import org.junit.Test; 19 | 20 | import java.text.ParseException; 21 | 22 | import static org.junit.Assert.assertEquals; 23 | 24 | public class CAEPTokenClaimsChangeTest { 25 | /** 26 | * Figure 5: Example: OIDC ID Token Claims Change - Required claims only 27 | */ 28 | 29 | @Test 30 | public void Figure5() throws ParseException, SIValidationException, ValidationException { 31 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 32 | .format(SubjectIdentifierFormats.JWT_ID) 33 | .issuer("https://idp.example.com/987654321/") 34 | .jwtID("f61t6e20zdo3px56gepu8rzlsp4c1dpc0fx7") 35 | .build(); 36 | 37 | JSONObject claims = new JSONObject(); 38 | claims.put("role", "ro-admin"); 39 | 40 | CAEPTokenClaimsChange evt = new CAEPTokenClaimsChange.Builder() 41 | .eventTimestamp(1615304991643L) 42 | .subject(subj) 43 | .claims(claims) 44 | .build(); 45 | 46 | JWTClaimsSet set = new JWTClaimsSet.Builder() 47 | .issuer("https://idp.example.com/987654321/") 48 | .jwtID("9afce1e4e642b165fcaacdd0e7aa4903") 49 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 50 | .audience("https://sp.example2.net/caep") 51 | .claim(SEToken.EVENTS_CLAIM, evt) 52 | .build(); 53 | 54 | final String figure_text = " {\n" + 55 | " \"iss\": \"https://idp.example.com/987654321/\",\n" + 56 | " \"jti\": \"9afce1e4e642b165fcaacdd0e7aa4903\",\n" + 57 | " \"iat\": 1615305159,\n" + 58 | " \"aud\": \"https://sp.example2.net/caep\",\n" + 59 | " \"events\": {\n" + 60 | " \"https://schemas.openid.net/secevent/caep/event-type/token-claims-change\": {\n" + 61 | " \"subject\": {\n" + 62 | " \"format\": \"jwt_id\",\n" + 63 | " \"iss\": \"https://idp.example.com/987654321/\",\n" + 64 | " \"jti\": \"f61t6e20zdo3px56gepu8rzlsp4c1dpc0fx7\"\n" + 65 | " },\n" + 66 | " \"event_timestamp\": 1615304991643,\n" + 67 | " \"claims\": {\n" + 68 | " \"role\": \"ro-admin\"\n" + 69 | " }\n" + 70 | " }\n" + 71 | " }\n" + 72 | " }\n"; 73 | 74 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 75 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 76 | assertEquals(figureJson, setJson); 77 | evt.validate(); 78 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 79 | 80 | } 81 | /** 82 | * Figure 6: Example: OIDC ID Token Claims Change - Optional claims 83 | */ 84 | 85 | @Test 86 | public void Figure6() throws ParseException, SIValidationException, ValidationException { 87 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 88 | .format(SubjectIdentifierFormats.JWT_ID) 89 | .issuer("https://idp.example.com/987654321/") 90 | .jwtID("f61t6e20zdo3px56gepu8rzlsp4c1dpc0fx7") 91 | .build(); 92 | 93 | JSONObject claims = new JSONObject(); 94 | claims.put("trusted_network", "false"); 95 | 96 | CAEPTokenClaimsChange evt = new CAEPTokenClaimsChange.Builder() 97 | .eventTimestamp(1615304991643L) 98 | .subject(subj) 99 | .claims(claims) 100 | .initiatingEntity(CAEPInitiatingEntity.POLICY) 101 | .reasonAdmin("User left trusted network: CorpNet3") 102 | .reasonUser("You're no longer connected to a trusted network.") 103 | .build(); 104 | 105 | JWTClaimsSet set = new JWTClaimsSet.Builder() 106 | .issuer("https://idp.example.com/987654321/") 107 | .jwtID("9afce1e4e642b165fcaacdd0e7aa4903") 108 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 109 | .audience("https://sp.example2.net/caep") 110 | .claim(SEToken.EVENTS_CLAIM, evt) 111 | .build(); 112 | 113 | final String figure_text = "{\n" + 114 | " \"iss\": \"https://idp.example.com/987654321/\",\n" + 115 | " \"jti\": \"9afce1e4e642b165fcaacdd0e7aa4903\",\n" + 116 | " \"iat\": 1615305159,\n" + 117 | " \"aud\": \"https://sp.example2.net/caep\",\n" + 118 | " \"events\": {\n" + 119 | " \"https://schemas.openid.net/secevent/caep/event-type/token-claims-change\": {\n" + 120 | " \"subject\": {\n" + 121 | " \"format\": \"jwt_id\",\n" + 122 | " \"iss\": \"https://idp.example.com/987654321/\",\n" + 123 | " \"jti\": \"f61t6e20zdo3px56gepu8rzlsp4c1dpc0fx7\"\n" + 124 | " },\n" + 125 | " \"event_timestamp\": 1615304991643,\n" + 126 | " \"initiating_entity\": \"policy\",\n" + 127 | " \"reason_admin\": \"User left trusted network: CorpNet3\",\n" + 128 | " \"reason_user\": \"You're no longer connected to a trusted network.\",\n" + 129 | " \"claims\": {\n" + 130 | " \"trusted_network\": \"false\"\n" + 131 | " }\n" + 132 | " }\n" + 133 | " }\n" + 134 | "}"; 135 | 136 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 137 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 138 | assertEquals(figureJson, setJson); 139 | evt.validate(); 140 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 141 | 142 | } 143 | 144 | /** 145 | * Figure 7: Example: SAML Assertion Claims Change - Required claims 146 | * only 147 | */ 148 | 149 | @Test 150 | public void Figure7() throws ParseException, SIValidationException, ValidationException { 151 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 152 | .format(SubjectIdentifierFormats.SAML_ASSERTION_ID) 153 | .issuer("https://idp.example.com/987654321/") 154 | .samlAssertionID("_a75adf55-01d7-dbd8372ebdfc") 155 | .build(); 156 | 157 | JSONObject claims = new JSONObject(); 158 | claims.put("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role", 159 | "ro-admin"); 160 | 161 | CAEPTokenClaimsChange evt = new CAEPTokenClaimsChange.Builder() 162 | .eventTimestamp(1615304991643L) 163 | .subject(subj) 164 | .member("claims", claims) 165 | .build(); 166 | 167 | JWTClaimsSet set = new JWTClaimsSet.Builder() 168 | .issuer("https://idp.example.com/987654321/") 169 | .jwtID("dae94fed5f459881efa38b65c6772ddc") 170 | .issueTime(DateUtils.fromSecondsSinceEpoch(1615305159L)) 171 | .audience("https://sp.example2.net/caep") 172 | .claim(SEToken.EVENTS_CLAIM, evt) 173 | .build(); 174 | 175 | final String figure_text = " {\n" + 176 | " \"iss\": \"https://idp.example.com/987654321/\",\n" + 177 | " \"jti\": \"dae94fed5f459881efa38b65c6772ddc\",\n" + 178 | " \"iat\": 1615305159,\n" + 179 | " \"aud\": \"https://sp.example2.net/caep\",\n" + 180 | " \"events\": {\n" + 181 | " \"https://schemas.openid.net/secevent/caep/event-type/token-claims-change\": {\n" + 182 | " \"subject\": {\n" + 183 | " \"format\": \"saml_assertion_id\",\n" + 184 | " \"issuer\": \"https://idp.example.com/987654321/\",\n" + 185 | " \"assertion_id\": \"_a75adf55-01d7-dbd8372ebdfc\"\n" + 186 | " },\n" + 187 | " \"event_timestamp\": 1615304991643,\n" + 188 | " \"claims\": {\n" + 189 | " \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role\": \"ro-admin\"\n" + 190 | " }\n" + 191 | " }\n" + 192 | " }\n" + 193 | " }\n"; 194 | 195 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 196 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 197 | assertEquals(figureJson, setJson); 198 | evt.validate(); 199 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 200 | } 201 | } -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/EventStreamTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.sse.SSEStreamUpdated; 14 | import com.sailpoint.sse.model.sse.SSEVerification; 15 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 16 | import com.sailpoint.ietf.subjectidentifiers.model.IssSubSubjectIdentifier; 17 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifierFormats; 18 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 19 | 20 | import org.junit.Test; 21 | 22 | import java.text.ParseException; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class EventStreamTests { 27 | /** 28 | * Figure 31: Example: Verification SET 29 | */ 30 | @Test() 31 | public void Figure31() throws ParseException, SIValidationException, ValidationException { 32 | 33 | SSEVerification evt = new SSEVerification.Builder() 34 | .state("VGhpcyBpcyBhbiBleGFtcGxlIHN0YXRlIHZhbHVlLgo=") 35 | .build(); 36 | 37 | JWTClaimsSet set = new JWTClaimsSet.Builder() 38 | .issuer("https://transmitter.example.com") 39 | .jwtID("123456") 40 | .issueTime(DateUtils.fromSecondsSinceEpoch(1493856000L)) 41 | .audience("receiver.example.com") 42 | .claim(SEToken.EVENTS_CLAIM, evt) 43 | .build(); 44 | 45 | final String figure_text = " {\n" + 46 | " \"jti\": \"123456\",\n" + 47 | " \"iss\": \"https://transmitter.example.com\",\n" + 48 | " \"aud\": \"receiver.example.com\",\n" + 49 | " \"iat\": 1493856000,\n" + 50 | " \"events\": {\n" + 51 | " \"https://schemas.openid.net/secevent/sse/event-type/verification\":{\n" + 52 | " \"state\": \"VGhpcyBpcyBhbiBleGFtcGxlIHN0YXRlIHZhbHVlLgo=\"\n" + 53 | " }\n" + 54 | " }\n" + 55 | " }"; 56 | 57 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 58 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 59 | assertEquals(figureJson, setJson); 60 | evt.validate(); 61 | 62 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 63 | } 64 | 65 | /** 66 | * Figure 32: Example: Stream Updated SET 67 | */ 68 | @Test() 69 | public void Figure32() throws ParseException, SIValidationException, ValidationException { 70 | IssSubSubjectIdentifier tenant = new IssSubSubjectIdentifier.Builder() 71 | .issuer("http://example.com/idp1") 72 | .subject("1234") 73 | .build(); 74 | 75 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 76 | .tenant(tenant) 77 | .build(); 78 | 79 | SSEStreamUpdated evt = new SSEStreamUpdated.Builder() 80 | .subject(subj) 81 | .status(StreamStatus.PAUSED) 82 | .reason("License is not valid") 83 | .build(); 84 | 85 | JWTClaimsSet set = new JWTClaimsSet.Builder() 86 | .issuer("https://transmitter.example.com") 87 | .jwtID("123456") 88 | .issueTime(DateUtils.fromSecondsSinceEpoch(1493856000L)) 89 | .audience("receiver.example.com") 90 | .claim(SEToken.EVENTS_CLAIM, evt) 91 | .build(); 92 | 93 | final String figure_text = "{\n" + 94 | " \"jti\": \"123456\",\n" + 95 | " \"iss\": \"https://transmitter.example.com\",\n" + 96 | " \"aud\": \"receiver.example.com\",\n" + 97 | " \"iat\": 1493856000,\n" + 98 | " \"events\": {\n" + 99 | " \"https://schemas.openid.net/secevent/sse/event-type/stream-updated\": {\n" + 100 | " \"subject\": {\n" + 101 | " \"tenant\" : {\n" + 102 | " \"format\": \"iss_sub\",\n" + 103 | " \"iss\" : \"http://example.com/idp1\",\n" + 104 | " \"sub\" : \"1234\"\n" + 105 | " }\n" + 106 | " },\n" + 107 | " \"status\": \"paused\",\n" + 108 | " \"reason\": \"License is not valid\"\n" + 109 | " }\n" + 110 | " }\n" + 111 | "}"; 112 | 113 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 114 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 115 | assertEquals(figureJson, setJson); 116 | evt.validate(); 117 | 118 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/GoogleTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.sse.model.risc.*; 14 | import com.sailpoint.ietf.subjectidentifiers.model.SubjectIdentifier; 15 | import com.sailpoint.ietf.subjectidentifiers.model.SIValidationException; 16 | import org.junit.Test; 17 | 18 | import java.text.ParseException; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | public class GoogleTests { 23 | /* 24 | * https://developers.google.com/identity/protocols/risc#handle_events 25 | * Note: This uses the original subject_type member and value format with dashes 26 | * rather than the newer (April 2021) format "iss_sub" 27 | */ 28 | @Test 29 | public void AccountDisabled() throws ParseException, SIValidationException, ValidationException { 30 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 31 | .member("subject_type", "iss-sub") 32 | .issuer("https://accounts.google.com/") 33 | .subject("7375626A656374") 34 | .build(); 35 | 36 | RISCAccountDisabled evt = new RISCAccountDisabled.Builder() 37 | .subject(subj) 38 | .reason(RISCAccountDisabledReasons.HIJACKING) 39 | .build(); 40 | 41 | JWTClaimsSet set = new JWTClaimsSet.Builder() 42 | .issuer("https://accounts.google.com/") 43 | .issueTime(DateUtils.fromSecondsSinceEpoch(1508184845L)) 44 | .audience("123456789-abcedfgh.apps.googleusercontent.com") 45 | .jwtID("756E69717565206964656E746966696572") 46 | .claim(SEToken.EVENTS_CLAIM, evt) 47 | .build(); 48 | 49 | final String figure_text = "{\n" + 50 | " \"iss\": \"https://accounts.google.com/\",\n" + 51 | " \"aud\": \"123456789-abcedfgh.apps.googleusercontent.com\",\n" + 52 | " \"iat\": 1508184845,\n" + 53 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 54 | " \"events\": {\n" + 55 | " \"https://schemas.openid.net/secevent/risc/event-type/account-disabled\": {\n" + 56 | " \"subject\": {\n" + 57 | " \"subject_type\": \"iss-sub\",\n" + 58 | " \"iss\": \"https://accounts.google.com/\",\n" + 59 | " \"sub\": \"7375626A656374\"\n" + 60 | " },\n" + 61 | " \"reason\": \"hijacking\"\n" + 62 | " }\n" + 63 | " }\n" + 64 | "}"; 65 | 66 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 67 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 68 | assertEquals(figureJson, setJson); 69 | subj.validate(); 70 | evt.validate(); 71 | 72 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/OpenIDSSEProfileTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.ietf.subjectidentifiers.model.*; 14 | import com.sailpoint.sse.model.caep.CAEPSessionRevoked; 15 | import com.sailpoint.sse.model.caep.CAEPTokenClaimsChange; 16 | import com.sailpoint.sse.model.risc.RISCAccountEnabled; 17 | import org.junit.Test; 18 | 19 | import java.text.ParseException; 20 | import java.util.Map; 21 | import java.util.stream.Collectors; 22 | import java.util.stream.IntStream; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | 26 | public class OpenIDSSEProfileTest { 27 | 28 | private void dumpMapRecursive(Map map, int level) { 29 | for (String k : map.keySet()) { 30 | Object o = map.get(k); 31 | // Can't use String.repeat() here until Java 11, we support 9+ at the moment. 32 | String sb = IntStream.range(0, level).mapToObj(i -> "\t").collect(Collectors.joining()); 33 | System.out.printf("%s%s -> (%s) %s%n", sb, k, o.getClass(), o); 34 | if (o instanceof JSONObject) { 35 | dumpMapRecursive((JSONObject) o, level + 1); 36 | } 37 | } 38 | } 39 | 40 | private void dumpJWT(JWTClaimsSet set) { 41 | Map map = set.getClaims(); 42 | dumpMapRecursive(map, 1); 43 | } 44 | 45 | 46 | /** 47 | * Figure 1: Example: Simple Subject 48 | */ 49 | @Test 50 | public void Figure1() throws ParseException, SIValidationException { 51 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 52 | .email("foo@example.com") 53 | .build(); 54 | 55 | JSONObject container = new JSONObject(); 56 | container.put("transferor", subj); 57 | 58 | final String figure_text = " {\"transferor\": {\n" + 59 | " \"format\": \"email\",\n" + 60 | " \"email\": \"foo@example.com\"\n" + 61 | " }}\n"; 62 | 63 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 64 | assertEquals(figureJson, container); 65 | subj.validate(); 66 | } 67 | 68 | /** 69 | * Figure 2: Example: Complex Subject 70 | */ 71 | @Test 72 | public void Figure2() throws ParseException, SIValidationException { 73 | EmailSubjectIdentifier user = new EmailSubjectIdentifier.Builder() 74 | .email("bar@example.com") 75 | .build(); 76 | 77 | IssSubSubjectIdentifier tenant = new IssSubSubjectIdentifier.Builder() 78 | .issuer("http://example.com/idp1") 79 | .subject("1234") 80 | .build(); 81 | 82 | SubjectIdentifier transferee = new SubjectIdentifier.Builder() 83 | .user(user) 84 | .tenant(tenant) 85 | .build(); 86 | 87 | JSONObject container = new JSONObject(); 88 | container.put("transferee", transferee); 89 | 90 | final String figure_text = " {\"transferee\": {\n" + 91 | " \"user\" : {\n" + 92 | " \"format\": \"email\",\n" + 93 | " \"email\": \"bar@example.com\"\n" + 94 | " },\n" + 95 | " \"tenant\" : {\n" + 96 | " \"format\": \"iss_sub\",\n" + 97 | " \"iss\" : \"http://example.com/idp1\",\n" + 98 | " \"sub\" : \"1234\"\n" + 99 | " }\n" + 100 | " }}"; 101 | 102 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 103 | assertEquals(figureJson, container); 104 | transferee.validate(); 105 | } 106 | 107 | /** 108 | * Figure 3: Example: 'jwt-id' Subject Identifier 109 | */ 110 | 111 | @Test 112 | public void Figure3() throws ParseException, SIValidationException { 113 | SubjectIdentifier jwtid = new SubjectIdentifier.Builder() 114 | .format(SubjectIdentifierFormats.JWT_ID) 115 | .issuer("https://idp.example.com/123456789/") 116 | .jwtID("B70BA622-9515-4353-A866-823539EECBC8") 117 | .build(); 118 | final String figure_text = " {\n" + 119 | " \"format\": \"jwt_id\",\n" + 120 | " \"iss\": \"https://idp.example.com/123456789/\",\n" + 121 | " \"jti\": \"B70BA622-9515-4353-A866-823539EECBC8\"\n" + 122 | " }\n"; 123 | 124 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 125 | assertEquals(figureJson, jwtid); 126 | jwtid.validate(); 127 | } 128 | 129 | /** 130 | * Figure 4: { 131 | * "format": "saml-assertion-id", 132 | * "issuer": "https://idp.example.com/123456789/", 133 | * "assertion_id": "_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" 134 | * } 135 | */ 136 | @Test 137 | public void Figure4() throws ParseException, SIValidationException { 138 | SubjectIdentifier jwtid = new SubjectIdentifier.Builder() 139 | .format(SubjectIdentifierFormats.SAML_ASSERTION_ID) 140 | .issuer("https://idp.example.com/123456789/") 141 | .samlAssertionID("_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6") 142 | .build(); 143 | 144 | final String figure_text = " {\n" + 145 | " \"format\": \"saml_assertion_id\",\n" + 146 | " \"issuer\": \"https://idp.example.com/123456789/\",\n" + 147 | " \"assertion_id\": \"_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6\"\n" + 148 | " }\n"; 149 | 150 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 151 | assertEquals(figureJson, jwtid); 152 | jwtid.validate(); 153 | } 154 | 155 | /** 156 | * Figure 5: Example: SET Containing a SSE Event with a Simple Subject Claim 157 | */ 158 | 159 | @Test 160 | public void Figure5() throws ParseException, SIValidationException, ValidationException { 161 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 162 | .email("foo@example.com") 163 | .build(); 164 | 165 | RISCAccountEnabled evt = new RISCAccountEnabled.Builder() 166 | .subject(subj) 167 | .build(); 168 | 169 | JWTClaimsSet set = new JWTClaimsSet.Builder() 170 | .issuer("https://idp.example.com/") 171 | .jwtID("756E69717565206964656E746966696572") 172 | .issueTime(DateUtils.fromSecondsSinceEpoch(1520364019)) 173 | .audience("636C69656E745F6964") 174 | .claim(SEToken.EVENTS_CLAIM, evt) 175 | .build(); 176 | 177 | final String figure_text = "{\n" + 178 | " \"iss\": \"https://idp.example.com/\",\n" + 179 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 180 | " \"iat\": 1520364019,\n" + 181 | " \"aud\": \"636C69656E745F6964\",\n" + 182 | " \"events\": {\n" + 183 | " \"https://schemas.openid.net/secevent/risc/event-type/account-enabled\": {\n" + 184 | " \"subject\": {\n" + 185 | " \"format\": \"email\",\n" + 186 | " \"email\": \"foo@example.com\"\n" + 187 | " }\n" + 188 | " }\n" + 189 | " }\n" + 190 | "}"; 191 | 192 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 193 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 194 | assertEquals(figureJson, setJson); 195 | evt.validate(); 196 | 197 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 198 | } 199 | 200 | 201 | /** 202 | * Figure 6: Example: SET Containing a SSE Event with a Complex Subject Claim 203 | */ 204 | 205 | @Test 206 | public void Figure6() throws ParseException, SIValidationException, ValidationException { 207 | IssSubSubjectIdentifier user = new IssSubSubjectIdentifier.Builder() 208 | .issuer("https://idp.example.com/3957ea72-1b66-44d6-a044-d805712b9288/") 209 | .subject("jane.smith@example.com") 210 | .build(); 211 | 212 | IssSubSubjectIdentifier device = new IssSubSubjectIdentifier.Builder() 213 | .issuer("https://idp.example.com/3957ea72-1b66-44d6-a044-d805712b9288/") 214 | .subject("e9297990-14d2-42ec-a4a9-4036db86509a") 215 | .build(); 216 | 217 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 218 | .user(user) 219 | .device(device) 220 | .build(); 221 | 222 | CAEPSessionRevoked evt = new CAEPSessionRevoked.Builder() 223 | .eventTimestamp(1615304991643L) 224 | .subject(subj) 225 | .build(); 226 | 227 | JWTClaimsSet set = new JWTClaimsSet.Builder() 228 | .issuer("https://idp.example.com/") 229 | .jwtID("756E69717565206964656E746966696572") 230 | .issueTime(DateUtils.fromSecondsSinceEpoch(1520364019)) 231 | .audience("636C69656E745F6964") 232 | .claim(SEToken.EVENTS_CLAIM, evt) 233 | .build(); 234 | 235 | final String figure_text = "{\n" + 236 | " \"iss\": \"https://idp.example.com/\",\n" + 237 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 238 | " \"iat\": 1520364019,\n" + 239 | " \"aud\": \"636C69656E745F6964\",\n" + 240 | " \"events\": {\n" + 241 | " \"https://schemas.openid.net/secevent/caep/event-type/session-revoked\": {\n" + 242 | " \"subject\": {\n" + 243 | " \"user\": {\n" + 244 | " \"format\": \"iss_sub\",\n" + 245 | " \"iss\": \"https://idp.example.com/3957ea72-1b66-44d6-a044-d805712b9288/\",\n" + 246 | " \"sub\": \"jane.smith@example.com\"\n" + 247 | " },\n" + 248 | " \"device\": {\n" + 249 | " \"format\": \"iss_sub\",\n" + 250 | " \"iss\": \"https://idp.example.com/3957ea72-1b66-44d6-a044-d805712b9288/\",\n" + 251 | " \"sub\": \"e9297990-14d2-42ec-a4a9-4036db86509a\"\n" + 252 | " }\n" + // closes device 253 | " },\n" + // closes subject 254 | " \"event_timestamp\": 1615304991643\n" + 255 | " }\n" + 256 | " }\n" + 257 | "}\n"; 258 | 259 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 260 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 261 | assertEquals(figureJson, setJson); 262 | evt.validate(); 263 | 264 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 265 | } 266 | 267 | 268 | /** 269 | * Figure 7: Example: SET Containing a SSE Event with a Simple Subject 270 | * and a Property Claim 271 | */ 272 | @Test() 273 | public void Figure7() throws ParseException, SIValidationException, ValidationException { 274 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 275 | .email("foo@example2.com") 276 | .build(); 277 | 278 | JSONObject newClaims = new JSONObject(); 279 | newClaims.put("ip_address", "123.45.67.89"); 280 | 281 | CAEPTokenClaimsChange evt = new CAEPTokenClaimsChange.Builder() 282 | .eventTimestamp(1615304991643L) 283 | .subject(subj) 284 | .claims(newClaims) 285 | .build(); 286 | 287 | JWTClaimsSet set = new JWTClaimsSet.Builder() 288 | .issuer("https://sp.example2.com/") 289 | .jwtID("756E69717565206964656E746966696572") 290 | .issueTime(DateUtils.fromSecondsSinceEpoch(1520364019)) 291 | .audience("636C69656E745F6964") 292 | .claim(SEToken.EVENTS_CLAIM, evt) 293 | .build(); 294 | 295 | final String figure_text = "{\n" + 296 | " \"iss\": \"https://sp.example2.com/\",\n" + 297 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 298 | " \"iat\": 1520364019,\n" + 299 | " \"aud\": \"636C69656E745F6964\",\n" + 300 | " \"events\": {\n" + 301 | " \"https://schemas.openid.net/secevent/caep/event-type/token-claims-change\": {\n" + 302 | " \"subject\": {\n" + 303 | " \"format\": \"email\",\n" + 304 | " \"email\": \"foo@example2.com\"\n" + 305 | " },\n" + 306 | " \"claims\": {\n" + 307 | " \"ip_address\" : \"123.45.67.89\"\n" + 308 | " },\n" + 309 | " \"event_timestamp\": 1615304991643\n" + 310 | " }\n" + 311 | " }\n" + 312 | "}"; 313 | 314 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 315 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 316 | assertEquals(figureJson, setJson); 317 | 318 | subj.validate(); 319 | evt.validate(); 320 | 321 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 322 | } 323 | 324 | /** 325 | * Figure 8: Example: SET Containing a SSE Event with a Proprietary format 326 | */ 327 | @Test() 328 | public void Figure8() throws ParseException, SIValidationException, ValidationException { 329 | SubjectIdentifier subj = new SubjectIdentifier.Builder() 330 | .member("format", "catalog_item") 331 | .member("catalog_id", "c0384/winter/2354122") 332 | .build(); 333 | 334 | JSONObject newClaims = new JSONObject(); 335 | newClaims.put("role", "ro-admin"); 336 | 337 | 338 | CAEPTokenClaimsChange evt = new CAEPTokenClaimsChange.Builder() 339 | .eventTimestamp(1600975810L) 340 | .subject(subj) 341 | .claims(newClaims) 342 | .build(); 343 | 344 | JWTClaimsSet set = new JWTClaimsSet.Builder() 345 | .issuer("https://myservice.example3.com/") 346 | .jwtID("756E69717565206964656E746966696534") 347 | .issueTime(DateUtils.fromSecondsSinceEpoch(15203800012L)) 348 | .audience("636C69656E745F6324") 349 | .claim(SEToken.EVENTS_CLAIM, evt) 350 | .build(); 351 | 352 | final String figure_text = "{\n" + 353 | " \"iss\": \"https://myservice.example3.com/\",\n" + 354 | " \"jti\": \"756E69717565206964656E746966696534\",\n" + 355 | " \"iat\": 15203800012,\n" + 356 | " \"aud\": \"636C69656E745F6324\",\n" + 357 | " \"events\": {\n" + 358 | " \"https://schemas.openid.net/secevent/caep/event-type/token-claims-change\": {\n" + 359 | " \"subject\": {\n" + 360 | " \"format\": \"catalog_item\",\n" + 361 | " \"catalog_id\": \"c0384/winter/2354122\"\n" + 362 | " },\n" + 363 | " \"event_timestamp\": 1600975810,\n" + 364 | " \"claims\": {\n" + 365 | " \"role\": \"ro-admin\"\n" + 366 | " }\n" + 367 | " }\n" + 368 | " }\n" + 369 | "}"; 370 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 371 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 372 | assertEquals(figureJson, setJson); 373 | 374 | subj.validate(); 375 | evt.validate(); 376 | 377 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 378 | } 379 | 380 | /** 381 | * SET Containing a SSE Event with a Simple Subject Claim 382 | * and an undefined event type 383 | */ 384 | 385 | @Test 386 | public void UndefinedEventParseTest() throws ParseException, SIValidationException, ValidationException { 387 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 388 | .email("foo@example.com") 389 | .build(); 390 | 391 | NonstandardSSEvent evt = new NonstandardSSEvent.Builder() 392 | .eventName("https://schemas.openid.net/secevent/unknown/event-type/unknown") 393 | .subject(subj) 394 | .build(); 395 | 396 | JWTClaimsSet set = new JWTClaimsSet.Builder() 397 | .issuer("https://idp.example.com/") 398 | .jwtID("756E69717565206964656E746966696572") 399 | .issueTime(DateUtils.fromSecondsSinceEpoch(1520364019)) 400 | .audience("636C69656E745F6964") 401 | .claim(SEToken.EVENTS_CLAIM, evt) 402 | .build(); 403 | 404 | final String figure_text = "{\n" + 405 | " \"iss\": \"https://idp.example.com/\",\n" + 406 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 407 | " \"iat\": 1520364019,\n" + 408 | " \"aud\": \"636C69656E745F6964\",\n" + 409 | " \"events\": {\n" + 410 | " \"https://schemas.openid.net/secevent/unknown/event-type/unknown\": {\n" + 411 | " \"subject\": {\n" + 412 | " \"format\": \"email\",\n" + 413 | " \"email\": \"foo@example.com\"\n" + 414 | " }\n" + 415 | " }\n" + 416 | " }\n" + 417 | "}"; 418 | 419 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 420 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 421 | assertEquals(figureJson, setJson); 422 | evt.validate(); 423 | 424 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/RISCProfileTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import com.nimbusds.jwt.JWTClaimsSet; 12 | import com.nimbusds.jwt.util.DateUtils; 13 | import com.sailpoint.ietf.subjectidentifiers.model.*; 14 | import com.sailpoint.sse.model.risc.*; 15 | import org.junit.Test; 16 | 17 | import java.text.ParseException; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | public class RISCProfileTests { 22 | /* 23 | * Figure 1: Example: Account Credential Change Required 24 | */ 25 | @Test 26 | public void Figure1() throws ParseException, SIValidationException, ValidationException { 27 | IssSubSubjectIdentifier subj = new IssSubSubjectIdentifier.Builder() 28 | .issuer("https://idp.example.com/") 29 | .subject("7375626A656374") 30 | .build(); 31 | 32 | RISCAccountCredentialChangeRequired evt = new RISCAccountCredentialChangeRequired.Builder() 33 | .subject(subj) 34 | .build(); 35 | 36 | JWTClaimsSet set = new JWTClaimsSet.Builder() 37 | .issuer("https://idp.example.com/") 38 | .jwtID("756E69717565206964656E746966696572") 39 | .issueTime(DateUtils.fromSecondsSinceEpoch(1508184845)) 40 | .audience("636C69656E745F6964") 41 | .claim(SEToken.EVENTS_CLAIM, evt) 42 | .build(); 43 | 44 | final String figure_text = " {\n" + 45 | " \"iss\": \"https://idp.example.com/\",\n" + 46 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 47 | " \"iat\": 1508184845,\n" + 48 | " \"aud\": \"636C69656E745F6964\",\n" + 49 | " \"events\": {\n" + 50 | " \"https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required\": {\n" + 51 | " \"subject\": {\n" + 52 | " \"format\": \"iss_sub\",\n" + 53 | " \"iss\": \"https://idp.example.com/\",\n" + 54 | " \"sub\": \"7375626A656374\"\n" + 55 | " }\n" + 56 | " }\n" + 57 | " }\n" + 58 | " }\n"; 59 | 60 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 61 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 62 | assertEquals(figureJson, setJson); 63 | subj.validate(); 64 | evt.validate(); 65 | 66 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 67 | } 68 | 69 | 70 | /** 71 | * Figure 2: Example: Account Disabled 72 | */ 73 | @Test 74 | public void Figure2() throws ParseException, SIValidationException, ValidationException { 75 | IssSubSubjectIdentifier subj = new IssSubSubjectIdentifier.Builder() 76 | .issuer("https://idp.example.com/") 77 | .subject("7375626A656374") 78 | .build(); 79 | 80 | RISCAccountDisabled evt = new RISCAccountDisabled.Builder() 81 | .reason(RISCAccountDisabledReasons.HIJACKING) 82 | .subject(subj) 83 | .build(); 84 | 85 | JWTClaimsSet set = new JWTClaimsSet.Builder() 86 | .issuer("https://idp.example.com/") 87 | .jwtID("756E69717565206964656E746966696572") 88 | .issueTime(DateUtils.fromSecondsSinceEpoch(1508184845)) 89 | .audience("636C69656E745F6964") 90 | .claim(SEToken.EVENTS_CLAIM, evt) 91 | .build(); 92 | 93 | final String figure_text = " {\n" + 94 | " \"iss\": \"https://idp.example.com/\",\n" + 95 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 96 | " \"iat\": 1508184845,\n" + 97 | " \"aud\": \"636C69656E745F6964\",\n" + 98 | " \"events\": {\n" + 99 | " \"https://schemas.openid.net/secevent/risc/event-type/account-disabled\": {\n" + 100 | " \"subject\": {\n" + 101 | " \"format\": \"iss_sub\",\n" + 102 | " \"iss\": \"https://idp.example.com/\",\n" + 103 | " \"sub\": \"7375626A656374\"\n" + 104 | " },\n" + 105 | " \"reason\": \"hijacking\"\n" + 106 | " }\n" + 107 | " }\n" + 108 | " }\n"; 109 | 110 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 111 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 112 | assertEquals(figureJson, setJson); 113 | subj.validate(); 114 | evt.validate(); 115 | 116 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 117 | } 118 | 119 | /** 120 | * Figure 3: Example: Identifier Changed 121 | */ 122 | @Test 123 | public void Figure3() throws ParseException, SIValidationException, ValidationException { 124 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 125 | .email("john.doe@example.com") 126 | .build(); 127 | 128 | RISCIdentifierChanged evt = new RISCIdentifierChanged.Builder() 129 | .subject(subj) 130 | .newValue("john.roe@example.com") 131 | .build(); 132 | 133 | JWTClaimsSet set = new JWTClaimsSet.Builder() 134 | .issuer("https://idp.example.com/") 135 | .jwtID("756E69717565206964656E746966696572") 136 | .issueTime(DateUtils.fromSecondsSinceEpoch(1508184845)) 137 | .audience("636C69656E745F6964") 138 | .claim(SEToken.EVENTS_CLAIM, evt) 139 | .build(); 140 | 141 | final String figure_text = " {\n" + 142 | " \"iss\": \"https://idp.example.com/\",\n" + 143 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 144 | " \"iat\": 1508184845,\n" + 145 | " \"aud\": \"636C69656E745F6964\",\n" + 146 | " \"events\": {\n" + 147 | " \"https://schemas.openid.net/secevent/risc/event-type/identifier-changed\": {\n" + 148 | " \"subject\": {\n" + 149 | " \"format\": \"email\",\n" + 150 | " \"email\": \"john.doe@example.com\"\n" + 151 | " },\n" + 152 | " \"new-value\": \"john.roe@example.com\"\n" + 153 | " }\n" + 154 | " }\n" + 155 | " }\n"; 156 | 157 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 158 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 159 | assertEquals(figureJson, setJson); 160 | subj.validate(); 161 | evt.validate(); 162 | 163 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 164 | } 165 | 166 | /** 167 | * Figure 4: Example: Identifier Recycled 168 | */ 169 | @Test 170 | public void Figure4() throws ParseException, SIValidationException, ValidationException { 171 | EmailSubjectIdentifier subj = new EmailSubjectIdentifier.Builder() 172 | .email("foo@example.com") 173 | .build(); 174 | 175 | RISCIdentifierRecycled evt = new RISCIdentifierRecycled.Builder() 176 | .subject(subj) 177 | .build(); 178 | 179 | JWTClaimsSet set = new JWTClaimsSet.Builder() 180 | .issuer("https://idp.example.com/") 181 | .jwtID("756E69717565206964656E746966696572") 182 | .issueTime(DateUtils.fromSecondsSinceEpoch(1508184845)) 183 | .audience("636C69656E745F6964") 184 | .claim(SEToken.EVENTS_CLAIM, evt) 185 | .build(); 186 | 187 | final String figure_text = " {\n" + 188 | " \"iss\": \"https://idp.example.com/\",\n" + 189 | " \"jti\": \"756E69717565206964656E746966696572\",\n" + 190 | " \"iat\": 1508184845,\n" + 191 | " \"aud\": \"636C69656E745F6964\",\n" + 192 | " \"events\": {\n" + 193 | " \"https://schemas.openid.net/secevent/risc/event-type/identifier-recycled\": {\n" + 194 | " \"subject\": {\n" + 195 | " \"format\": \"email\",\n" + 196 | " \"email\": \"foo@example.com\"\n" + 197 | " }\n" + 198 | " }\n" + 199 | " }\n" + 200 | " }\n"; 201 | 202 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 203 | final JSONObject setJson = new JSONObject(set.toJSONObject()); 204 | assertEquals(figureJson, setJson); 205 | subj.validate(); 206 | evt.validate(); 207 | 208 | JWTClaimsSet parsedSet = SEToken.parse(figure_text); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/test/java/com/sailpoint/sse/model/TransmitterConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 SailPoint Technologies, Inc. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.sailpoint.sse.model; 8 | 9 | import com.nimbusds.jose.shaded.json.JSONObject; 10 | import com.nimbusds.jose.util.JSONObjectUtils; 11 | import org.junit.Test; 12 | 13 | import java.text.ParseException; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | 19 | public class TransmitterConfigTest { 20 | 21 | @Test() 22 | public void Figure15() throws ParseException { 23 | final String iss = "https://tr.example.com"; 24 | final String ssePrefix = "/sse/mgmt"; 25 | final String[] deliveryMethods = {DeliveryMethods.PUSH.toString(), 26 | DeliveryMethods.POLL.toString()}; 27 | final ArrayList criticalMembers = new ArrayList<>(Arrays.asList("tenant", "user")); 28 | 29 | TransmitterConfig txCfg = new TransmitterConfig.Builder() 30 | .issuer(iss) 31 | .jwksUri(iss + "/jwks.json") 32 | .deliveryMethods(Arrays.asList(deliveryMethods)) 33 | .configurationEndpoint(iss + ssePrefix + "/stream") 34 | .statusEndpoint(iss + ssePrefix + "/status") 35 | .addSubjectEndpoint(iss + ssePrefix + "/subject:add") 36 | .removeSubjectEndpoint(iss + ssePrefix + "/subject:remove") 37 | .verificationEndpoint(iss + ssePrefix + "/verification") 38 | .criticalSubjectMembers(criticalMembers) 39 | .build(); 40 | 41 | final String figure_text = " {\n" + 42 | " \"issuer\":\n" + 43 | " \"https://tr.example.com\",\n" + 44 | " \"jwks_uri\":\n" + 45 | " \"https://tr.example.com/jwks.json\",\n" + 46 | " \"delivery_methods_supported\": [\n" + 47 | " \"https://schemas.openid.net/secevent/risc/delivery-method/push\",\n" + 48 | " \"https://schemas.openid.net/secevent/risc/delivery-method/poll\"],\n" + 49 | " \"configuration_endpoint\":\n" + 50 | " \"https://tr.example.com/sse/mgmt/stream\",\n" + 51 | " \"status_endpoint\":\n" + 52 | " \"https://tr.example.com/sse/mgmt/status\",\n" + 53 | " \"add_subject_endpoint\":\n" + 54 | " \"https://tr.example.com/sse/mgmt/subject:add\",\n" + 55 | " \"remove_subject_endpoint\":\n" + 56 | " \"https://tr.example.com/sse/mgmt/subject:remove\",\n" + 57 | " \"verification_endpoint\":\n" + 58 | " \"https://tr.example.com/sse/mgmt/verification\",\n" + 59 | " \"critical_subject_members\": [ \"tenant\", \"user\" ]\n" + 60 | " }"; 61 | 62 | final JSONObject figureJson = new JSONObject(JSONObjectUtils.parse(figure_text)); 63 | assertEquals(figureJson, txCfg); 64 | } 65 | 66 | } 67 | --------------------------------------------------------------------------------