Compare commits
	
		
			25 Commits
		
	
	
		
			v1.2.0-bet
			...
			v1.2.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e6f9c30611 | ||
|  | 06b2c787d3 | ||
|  | 5645f69208 | ||
|  | 92cf5c6b21 | ||
|  | ad1b91f370 | ||
|  | e9c29f1c03 | ||
|  | f13748abeb | ||
|  | 7208c7e456 | ||
|  | 8857f636d6 | ||
|  | d9fc41e8c7 | ||
|  | da08e0b4ad | ||
|  | 11fc8f08b0 | ||
|  | af4d734105 | ||
|  | 0f4f5ac81b | ||
|  | 8c4ddd2e65 | ||
|  | cb8049b54a | ||
|  | 99b7d9f27d | ||
|  | ded5e3db5e | ||
|  | b892d19023 | ||
|  | 026a2e82d9 | ||
|  | b881f73798 | ||
|  | 99d793b5ed | ||
|  | cb02f62b9d | ||
|  | 29017fbe1e | ||
|  | 20a4d8dd91 | 
| @@ -64,28 +64,28 @@ storage.provider.sqlite.database: '/path/to/mxisd.db' | ||||
| # LDAP Backend # | ||||
| ################ | ||||
| # If you would like to integrate with your AD/Samba/LDAP server, | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/ldap.md | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/ldap.md | ||||
|  | ||||
|  | ||||
| ############### | ||||
| # SQL Backend # | ||||
| ############### | ||||
| # If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB, | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/sql.md | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/sql.md | ||||
|  | ||||
|  | ||||
| ################ | ||||
| # REST Backend # | ||||
| ################ | ||||
| # If you would like to integrate with an existing web service/webapp, | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/rest.md | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/rest.md | ||||
|  | ||||
|  | ||||
| ################################################# | ||||
| # Notifications for invites/addition to profile # | ||||
| ################################################# | ||||
| # If you would like to change the content, | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/notifications/template-generator.md | ||||
| # see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/notification/template-generator.md | ||||
| # | ||||
| #### E-mail invite sender | ||||
| # | ||||
|   | ||||
							
								
								
									
										26
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -41,17 +41,25 @@ def debBuildDataPath = "${debBuildBasePath}${debDataPath}" | ||||
| def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}" | ||||
|  | ||||
| def dockerImageName = "kamax/mxisd" | ||||
| def dockerImageTag = "${dockerImageName}:${gitVersion()}" | ||||
| def dockerImageTag = "${dockerImageName}:${mxisdVersion()}" | ||||
|  | ||||
| String mxisdVersion() { | ||||
|     def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") | ||||
|  | ||||
|     String version = System.getenv('MXISD_BUILD_VERSION') | ||||
|     if (version == null || version.size() == 0) { | ||||
|         version = gitVersion() | ||||
|     } | ||||
|     return versionPattern.matcher(version).matches() ? version.substring(1) : version | ||||
| } | ||||
|  | ||||
| String gitVersion() { | ||||
|     def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") | ||||
|     ByteArrayOutputStream out = new ByteArrayOutputStream() | ||||
|     exec { | ||||
|         commandLine = ['git', 'describe', '--tags', '--always', '--dirty'] | ||||
|         standardOutput = out | ||||
|     } | ||||
|     def v = out.toString().replace(System.lineSeparator(), '') | ||||
|     return versionPattern.matcher(v).matches() ? v.substring(1) : v | ||||
|     return out.toString().replace(System.lineSeparator(), ''); | ||||
| } | ||||
|  | ||||
| buildscript { | ||||
| @@ -99,8 +107,8 @@ dependencies { | ||||
|     compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1' | ||||
|  | ||||
|     // E-mail sending | ||||
|     compile 'com.sun.mail:javax.mail:1.5.6' | ||||
|     compile 'javax.mail:javax.mail-api:1.5.6' | ||||
|     compile 'com.sun.mail:javax.mail:1.6.2' | ||||
|     compile 'javax.mail:javax.mail-api:1.6.2' | ||||
|  | ||||
|     // Google Firebase Authentication backend | ||||
|     compile 'com.google.firebase:firebase-admin:5.3.0' | ||||
| @@ -126,8 +134,12 @@ dependencies { | ||||
|     // SendGrid SDK to send emails from GCE | ||||
|     compile 'com.sendgrid:sendgrid-java:2.2.2' | ||||
|  | ||||
|     // ZT-Exec for exec identity store | ||||
|     compile 'org.zeroturnaround:zt-exec:1.10' | ||||
|  | ||||
|     testCompile 'junit:junit:4.12' | ||||
|     testCompile 'com.github.tomakehurst:wiremock:2.8.0' | ||||
|     testCompile 'com.unboundid:unboundid-ldapsdk:4.0.9' | ||||
| } | ||||
|  | ||||
| springBoot { | ||||
| @@ -151,7 +163,7 @@ processResources { | ||||
|  | ||||
| task buildDeb(dependsOn: build) { | ||||
|     doLast { | ||||
|         def v = gitVersion() | ||||
|         def v = mxisdVersion() | ||||
|         println "Version for package: ${v}" | ||||
|         mkdir distDir | ||||
|         mkdir debBuildBasePath | ||||
|   | ||||
| @@ -74,7 +74,15 @@ See your Identity store [documentation](../stores/README.md) on how to enable th | ||||
|  | ||||
|  | ||||
| ## Advanced | ||||
| The Authentication feature allows users to login to their Homeserver by using their 3PIDs in a configured Identity store. | ||||
| The Authentication feature allows users to: | ||||
| - Rewrite usernames matching a pattern to be mapped to another username via a 3PID. | ||||
| - login to their Homeserver by using their 3PIDs in a configured Identity store. | ||||
|  | ||||
| This feature also allows to work around the following issues: | ||||
| - Lowercase all usernames for synapse, allowing case-insensitive login | ||||
| - Unable to login on synapse if username is numerical | ||||
| - Any generic transformation of username prior to sending to synapse, bypassing the restriction that password providers | ||||
| cannot change the localpart being authenticated. | ||||
|  | ||||
| ### Overview | ||||
| This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/login` as depicted below: | ||||
| @@ -109,10 +117,10 @@ Steps of user authentication using a 3PID: | ||||
| 4. The response from the Homeserver is sent back to the client, believing it was the HS which directly answered. | ||||
|  | ||||
| ### Requirements | ||||
| - [Basic Authentication configured and working](#basic) | ||||
| - Reverse proxy setup | ||||
| - Homeserver | ||||
| - Compatible [Identity store](../stores/README.md) | ||||
| - [Basic Authentication configured and working](#basic) | ||||
| - Client and Homeserver using the [C2S API r0.4.x](https://matrix.org/docs/spec/client_server/r0.4.0.html) or later | ||||
| - Reverse proxy setup | ||||
|  | ||||
| ### Configuration | ||||
| #### Reverse Proxy | ||||
| @@ -153,3 +161,40 @@ In case the hostname is the same as your Matrix domain and `server.name` is not | ||||
| `matrix.domain` and will still probably have the correct value. | ||||
|  | ||||
| `value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`. | ||||
|  | ||||
| #### Username rewrite | ||||
| In mxisd config: | ||||
| ```yaml | ||||
| auth: | ||||
|   rewrite: | ||||
|     user: | ||||
|       rules: | ||||
|         - regex: <your regexp> | ||||
|           medium: 'your.custom.medium.type' | ||||
| ``` | ||||
| `rules` takes a list of rules. Rules have two properties: | ||||
| - `regexp`: The regex pattern to match. This **MUST** match the full string. See [Java regex](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) for syntax. | ||||
| - `medium`: Custom 3PID type that will be used in the 3PID lookup. This can be anything you want and needs to be supported | ||||
| by your Identity store config and/or code. | ||||
|  | ||||
| Rules are matched in listed order. | ||||
|  | ||||
| Common regexp patterns: | ||||
| - Numerical usernames: `[0-9]+` | ||||
|  | ||||
| ##### LDAP Example | ||||
| If your users use their numerical employee IDs, which cannot be used with synapse, you can make it work with (relevant config only): | ||||
| ```yaml | ||||
| auth: | ||||
|   rewrite: | ||||
|     user: | ||||
|       rules: | ||||
|         - regex: '[0-9]+' | ||||
|           medium: 'kmx.employee.id' | ||||
|            | ||||
| ldap: | ||||
|   attribute: | ||||
|     threepid: | ||||
|       kmx.employee.id: | ||||
|         - 'ldapAttributeForEmployeeId' | ||||
| ``` | ||||
|   | ||||
| @@ -1,12 +1,16 @@ | ||||
| # Profile enhancement | ||||
| **WARNING**: Alpha feature, not officially supported. Do not use. | ||||
| # Profile | ||||
| **WARNING**: The following sub-features are considered experimental and not officially supported. Use at your own peril. | ||||
|  | ||||
| This feature allows to enhance a profile query with more info than just Matrix ID and Display name, allowing for custom | ||||
| applications to retrieve custom data not currently provided by synapse, per example. | ||||
| ## Public Profile enhancement | ||||
| This feature allows to enhance a public profile query with more info than just Matrix ID and Display name, allowing for | ||||
| custom applications to retrieve custom data not currently provided by synapse, per example. | ||||
|  | ||||
| ## Configuration | ||||
| ### Reverse proxy | ||||
| #### Apache | ||||
| **WARNING**: This information can be queried without authentication as per the specification. Do not enable unless in a | ||||
| controlled environment. | ||||
|  | ||||
| ### Configuration | ||||
| #### Reverse proxy | ||||
| ##### Apache | ||||
| ```apache | ||||
| ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1" | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										10
									
								
								docs/features/profile.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docs/features/profile.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # Profile | ||||
| The profile feature does not do anything on its own and acts as a support feature for others, allowing to retrieve | ||||
| information about a user based on its Matrix ID by querying enabled [Identity stores](../stores/README.md). | ||||
|  | ||||
| Currently supported: | ||||
| - Display name | ||||
| - 3PIDs | ||||
| - Roles/Groups | ||||
|  | ||||
| Experimental sub-features are also available. See [the dedicated document](experimental/profile.md). | ||||
| @@ -29,9 +29,10 @@ If you would like a high-level view of the infrastructure and how each feature i | ||||
|  | ||||
| ## Install | ||||
| Install via: | ||||
| - [Docker image](install/docker.md) | ||||
| - [Debian package](install/debian.md) | ||||
| - [ArchLinux](install/archlinux.md) | ||||
| - [Docker image](install/docker.md) | ||||
| - [NixOS](install/nixos.md) | ||||
| - [Sources](build.md) | ||||
|  | ||||
| See the [Latest release](https://github.com/kamax-matrix/mxisd/releases/latest) for links to each. | ||||
|   | ||||
| @@ -1,4 +1,7 @@ | ||||
| # Debian package | ||||
| ## Requirements | ||||
| - Any distribution that supports Java 8 | ||||
|  | ||||
| ## Install | ||||
| 1. Download the [latest release](https://github.com/kamax-matrix/mxisd/releases/latest) | ||||
| 2. Run: | ||||
|   | ||||
							
								
								
									
										8
									
								
								docs/install/nixos.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/install/nixos.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| # NixOS package | ||||
| mxisd is available as a NixOS package in the official repos. | ||||
|  | ||||
| It is maintained by [maximilian](https://matrix.to/#/@maximilian:transformierende-gesellschaft.org), a community member.   | ||||
|  | ||||
| Related resources: | ||||
| - [NixOS](https://nixos.org/) | ||||
| - [The module definition](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/mxisd.nix) | ||||
| @@ -1,7 +1,8 @@ | ||||
| # Identity Stores | ||||
| - [Synapse](synapse.md) | ||||
| - [LDAP-based](ldap.md) | ||||
| - [SQL Databases](sql.md) | ||||
| - [Website / Web service / Web app](rest.md) | ||||
| - [Google Firebase](firebase.md) | ||||
| - [Wordpress](wordpress.md) | ||||
| - [Synapse](synapse.md) - Turn your SynapseDB into a self-contained Identity store | ||||
| - [LDAP-based](ldap.md) - Any LDAP-based product like Active Directory, Samba, NetIQ, OpenLDAP | ||||
| - [SQL Databases](sql.md) - Most common databases like MariaDB, MySQL, PostgreSQL, SQLite | ||||
| - [Website / Web service / Web app](rest.md) - Arbitrary REST endpoints | ||||
| - [Executables](exec.md) - Run arbitrary executables with configurable stdin, arguments, environment and stdout | ||||
| - [Wordpress](wordpress.md) - Connect your Wordpress-powered website DB | ||||
| - [Google Firebase](firebase.md) - Use your Firebase users (with experimental SSO support!) | ||||
|   | ||||
							
								
								
									
										470
									
								
								docs/stores/exec.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										470
									
								
								docs/stores/exec.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,470 @@ | ||||
| # Exec Identity Store | ||||
| - [Features](#features) | ||||
| - [Overview](#overview) | ||||
| - [Configuration](#configuration) | ||||
|   - [Global](#global) | ||||
|     - [Tokens](#tokens) | ||||
|   - [Executable](#executable) | ||||
|     - [Input](#input) | ||||
|     - [Output](#output) | ||||
|   - [Examples](#examples) | ||||
|   - [Per-Feature](#per-feature) | ||||
| - [Authentication](#authentication) | ||||
|   - [Tokens](#tokens-1) | ||||
|   - [Input](#input-1) | ||||
|   - [Output](#output-1) | ||||
| - [Directory](#directory) | ||||
|   - [Tokens](#tokens-2) | ||||
|   - [Input](#input-2) | ||||
|   - [Output](#output-2) | ||||
| - [Identity](#identity) | ||||
|   - [Single Lookup](#single-lookup) | ||||
|     - [Tokens](#tokens-3) | ||||
|     - [Input](#input-3) | ||||
|     - [Output](#output-3) | ||||
|   - [Bulk Lookup](#bulk-lookup) | ||||
|     - [Tokens](#tokens-4) | ||||
|     - [Input](#input-4) | ||||
|     - [Output](#output-4) | ||||
| - [Profile](#profile) | ||||
|   - [Tokens](#tokens-5) | ||||
|   - [Input](#input-5) | ||||
|   - [Output](#output-5) | ||||
|    | ||||
| --- | ||||
|  | ||||
| ## Features | ||||
| |                       Name                      | Supported | | ||||
| |-------------------------------------------------|-----------| | ||||
| | [Authentication](../features/authentication.md) | Yes       | | ||||
| | [Directory](../features/directory.md)           | Yes       | | ||||
| | [Identity](../features/identity.md)             | Yes       | | ||||
| | [Profile](#profile)                             | Yes       | | ||||
|  | ||||
| This Identity Store lets you run arbitrary commands to handle the various requests in each support feature.   | ||||
| It is the most versatile Identity store of mxisd, allowing you to connect any kind of logic with any executable/script. | ||||
|  | ||||
| ## Overview | ||||
| Each request can be mapping to a fully customizable command configuration.   | ||||
| The various parameters can be provided via any combination of: | ||||
| - [Standard Input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) | ||||
| - [Command-line arguments](https://en.wikipedia.org/wiki/Command-line_interface#Arguments) | ||||
| - [Environment variables](https://en.wikipedia.org/wiki/Environment_variable) | ||||
|  | ||||
| Each of those supports a set of customizable token which will be replaced prior to running the command, allowing to | ||||
| provide the input values in any number of ways. | ||||
|  | ||||
| Success and data will be provided via any combination of: | ||||
| - [Exit status](https://en.wikipedia.org/wiki/Exit_status) | ||||
| - [Standard Output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)) | ||||
|  | ||||
| Each of those supports a set of configuration item to decide how to process the value and/or in which format. | ||||
|  | ||||
| All values, inputs and outputs are UTF-8 encoded. | ||||
|  | ||||
| ## Configuration | ||||
| Each feature comes with a set of possible lookup/action which is mapped to a generic configuration item block.   | ||||
| We will use the term `Executable` for each lookup/action and `Processor` for each configuration block. | ||||
|  | ||||
| ### Global | ||||
| ```yaml | ||||
| exec.enabled: <boolean> | ||||
| ``` | ||||
| Enable/disable the Identity store at a global/default level. Each feature can still be individually enabled/disabled. | ||||
|  | ||||
| #### Tokens | ||||
| The following options allow to globally set tokens for value replacement across all features and processors config.   | ||||
| Not all features use all tokens, and each feature might also have its own specific tokens. See each feature documentation. | ||||
|    | ||||
| They can be set within the following scope: | ||||
|  | ||||
| ```yaml | ||||
| exec.token.<token>: '<value>' | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| The following tokens and default values are available: | ||||
| ```yaml | ||||
| localpart: '{localpart}' | ||||
| ``` | ||||
| Localpart of Matrix User IDs | ||||
|  | ||||
| ```yaml | ||||
| domain: '{domain}' | ||||
| ``` | ||||
| Domain of Matrix User IDs | ||||
|  | ||||
| ```yaml | ||||
| mxid: '{mxid}' | ||||
| ``` | ||||
| Full representation of Matrix User IDs | ||||
|  | ||||
| ```yaml | ||||
| medium: '{medium}' | ||||
| ``` | ||||
| Medium of 3PIDs | ||||
|  | ||||
| ```yaml | ||||
| address: '{address}' | ||||
| ``` | ||||
| Address of 3PIDs | ||||
|  | ||||
| ```yaml | ||||
| type: '{type}' | ||||
| ``` | ||||
| Type of query | ||||
|  | ||||
| ```yaml | ||||
| query: '{query}' | ||||
| ``` | ||||
| Query value | ||||
|  | ||||
| ### Executable | ||||
| *Executable*s have the following options: | ||||
| ```yaml | ||||
| command: '/path/to/executableOrScript' | ||||
|  | ||||
| ``` | ||||
| Set the executable (relative or absolute) path to be executed. If no command is given, the action will return a "neutral" | ||||
| result if possible or be skipped altogether. | ||||
|  | ||||
| --- | ||||
|  | ||||
| Command line arguments can be given via a list via both YAML formats: | ||||
| ```yaml | ||||
| args: | ||||
|  - '-t' | ||||
|  - '{token}' | ||||
|  - '-v' | ||||
|  - 'value' | ||||
| ``` | ||||
| or | ||||
| ```yaml | ||||
| args: ['-t', '{token}', '-v', 'value] | ||||
| ``` | ||||
| Each argument will be processed for token replacement. | ||||
|  | ||||
| --- | ||||
|  | ||||
| Environment variables can be given as key/value pairs: | ||||
| ```yaml | ||||
| env: | ||||
|   ENV_VAR_1: 'value' | ||||
|   ENV_VAR_2: '{token}' | ||||
| ```  | ||||
| Each variable value will be processed for token replacement. | ||||
|  | ||||
| #### Input | ||||
| Standard input can be configured in the namespaces `input` with: | ||||
| - `type`: The format to use | ||||
| - `template`: The full or partial template with tokens to be used when generating the input | ||||
|  | ||||
| Not all features and *Executable*s allow for a template to be provided.   | ||||
| Templates for listed-based input are not supported at this time.   | ||||
| Default templates may be provided per *Executable*. | ||||
|  | ||||
| The following types are available: | ||||
| - `json`: Use JSON format, shared with the [REST Identity Store](rest.md) | ||||
| - `plain`: Use a custom multi-lines, optionally tab-separated input | ||||
|  | ||||
| #### Output | ||||
| Standard output can be configured in the namespaces `output` with: | ||||
| - `type`: The format to use | ||||
| - `template`: The full or partial template with tokens to be used when processing the output | ||||
|  | ||||
| Not all features and *Executable*s allow for a template to be provided.   | ||||
| Templates for listed-based output are not supported at this time.   | ||||
| Default templates may be provided per *Executable*. | ||||
|  | ||||
| The following types are available: | ||||
| - `json`: Use JSON format, shared with the [REST Identity Store](rest.md) | ||||
| - `plain`: Use a custom multi-lines, optionally tab-separated output | ||||
|  | ||||
| ### Examples | ||||
| #### Basic | ||||
| ```yaml | ||||
| exec.auth.enabled: true | ||||
| exec.auth.command: '/opt/mxisd-exec/auth.sh' | ||||
| exec.auth.args: ['{localpart}'] | ||||
| exec.auth.input.type: 'plain' | ||||
| exec.auth.input.template: '{password}' | ||||
| exec.auth.env: | ||||
|   DOMAIN: '{domain}' | ||||
| ``` | ||||
| With Authentication enabled, run `/opt/mxisd-exec/auth.sh` when validating credentials, providing: | ||||
| - A single command-line argument to provide the `localoart` as username  | ||||
| - A plain text string with the password token for standard input, which will be replaced by the password to check | ||||
| - A single environment variable `DOMAIN` containing Matrix ID domain, if given | ||||
|  | ||||
| The command will use the default values for: | ||||
| - Success exit status of `0` | ||||
| - Failure exit status of `1` | ||||
| - Any other exit status considered as error | ||||
| - The standard output processing as not processed | ||||
|  | ||||
| #### Advanced | ||||
| Given the fictional `placeholder` feature: | ||||
| ```yaml | ||||
| exec.enabled: true | ||||
| exec.token.mxid: '{matrixId}' | ||||
|  | ||||
| exec.placeholder.token.localpart: '{username}' | ||||
| exec.placeholder.command: '/path/to/executable' | ||||
| exec.placeholder.args: | ||||
|   - '-u' | ||||
|   - '{username}' | ||||
| exec.placeholder.env: | ||||
|   MATRIX_DOMAIN: '{domain}' | ||||
|   MATRIX_USER_ID: '{matrixId}' | ||||
|    | ||||
| exec.placeholder.output.type: 'json' | ||||
| exec.placeholder.exit.success: [0, 128] | ||||
| exec.placeholder.exit.failure: [1, 129] | ||||
| ``` | ||||
| With: | ||||
| - The Identity store enabled for all features | ||||
| - A global specific token `{matrixId}` for Matrix User IDs, replacing the default `{mxid}` | ||||
|  | ||||
| Running `/path/to/executable` providing: | ||||
| - A custom token for localpart, `{username}`, used as a 2nd command-line argument | ||||
| - An extracted Matrix User ID `localpart` provided as the second command line argument, the first one being `-u`  | ||||
| - A password, the extracted Matrix `domain` and the full User ID as arbitrary environment variables, respectively | ||||
|   `PASSWORD`, `MATRIX_DOMAIN` and `MATRIX_USER_ID` | ||||
|  | ||||
| After execution: | ||||
| - Process stdout as [JSON](https://en.wikipedia.org/wiki/JSON) | ||||
| - Consider exit status `0` and `128` as success and try to process the stdout for data | ||||
| - Consider exit status `1` and `129` as failure and try to process the stdout for error code and message | ||||
|  | ||||
| ### Per Feature | ||||
| See each dedicated [Feature](#features) section. | ||||
|  | ||||
| ## Authentication | ||||
| The Authentication feature can be enabled/disabled using: | ||||
| ```yaml | ||||
| exec.auth.enabled: <true/false> | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| This feature provides a single *Executable* under the namespace: | ||||
| ```yaml | ||||
| exec.auth: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| ### Tokens | ||||
| The following tokens/default values are specific to this feature: | ||||
| ```yaml | ||||
| password: '{password}' | ||||
| ``` | ||||
| The provided password | ||||
|  | ||||
| ### Input | ||||
| Supported input types and default templates: | ||||
|  | ||||
| #### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| #### Plain (`plain`) | ||||
| Default template: | ||||
| ``` | ||||
| {localpart} | ||||
| {domain} | ||||
| {mxid} | ||||
| {password} | ||||
| ``` | ||||
|  | ||||
| ### Output | ||||
| Supported output types and default templates: | ||||
|  | ||||
| #### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| #### Plain (`plain`) | ||||
| **NOTE:** This has limited support. Use the JSON type for full support. | ||||
|  | ||||
| Default template: | ||||
| ``` | ||||
| [success status, true or 1 are interpreted as success] | ||||
| [display name of the user] | ||||
| ``` | ||||
|  | ||||
| ## Directory | ||||
| The Directory feature can be enabled/disabled using: | ||||
| ```yaml | ||||
| exec.directory.enabled: <true/false> | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| Two search types configuration namespace are available, using the same input/output formats and templates: | ||||
|  | ||||
| By name: | ||||
| ```yaml | ||||
| exec.directory.search.byName: | ||||
|   ... | ||||
| ``` | ||||
| By 3PID: | ||||
| ```yaml | ||||
| exec.directory.search.byThreepid: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| #### Tokens | ||||
| No specific tokens are available. | ||||
|  | ||||
| #### Input | ||||
| Supported input types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| Default template: | ||||
| ``` | ||||
| [type of search, following the REST Identity store format] | ||||
| [query string] | ||||
| ``` | ||||
|  | ||||
| #### Output | ||||
| Supported output types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| **Not supported at this time.** Use the JSON type. | ||||
|  | ||||
| ## Identity | ||||
| The Identity feature can be enabled/disabled using: | ||||
| ```yaml | ||||
| exec.identity.enabled: <true/false> | ||||
| ``` | ||||
|  | ||||
| ### Single lookup | ||||
| Configuration namespace: | ||||
| ```yaml | ||||
| exec.identity.lookup.single: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| #### Tokens | ||||
| No specific tokens are available. | ||||
|  | ||||
| #### Input | ||||
| Supported input types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| Default template: | ||||
| ``` | ||||
| {medium} | ||||
| {address} | ||||
| ``` | ||||
|  | ||||
| #### Output | ||||
| Supported output types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| Default template: | ||||
| ``` | ||||
| [User ID type, as documented in the REST Identity Store] | ||||
| [User ID value] | ||||
| ``` | ||||
|  | ||||
| The User ID type will default to `localpart` if: | ||||
| - Only one line is returned | ||||
| - The first line is empty | ||||
|  | ||||
| ### Bulk lookup | ||||
| Configuration namespace: | ||||
| ```yaml | ||||
| exec.identity.lookup.bulk: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| #### Tokens | ||||
| No specific tokens are available. | ||||
|  | ||||
| #### Input | ||||
| Supported input types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| **NOTE:** Custom Templates are not supported. | ||||
|  | ||||
| Same as the [REST Identity Store](rest.md). | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| **Not supported at this time.** Use the JSON type. | ||||
|  | ||||
| #### Output | ||||
| Supported output types and default templates: | ||||
|  | ||||
| ##### JSON (`json`) | ||||
| **NOTE:** Custom Templates are not supported. | ||||
|  | ||||
| Same as the [REST Identity Store](rest.md). | ||||
|  | ||||
| ##### Plain (`plain`) | ||||
| **Not supported at this time.** Use the JSON type. | ||||
|  | ||||
| ## Profile | ||||
| The Profile feature can be enabled/disabled using: | ||||
| ```yaml | ||||
| exec.profile.enabled: <true/false> | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| The following *Executable*s namespace are available, share the same input/output formats and templates: | ||||
|  | ||||
| Get Display name: | ||||
| ```yaml | ||||
| exec.profile.displayName: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| Get 3PIDs: | ||||
| ```yaml | ||||
| exec.profile.threePid: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
| Get Roles: | ||||
| ```yaml | ||||
| exec.profile.role: | ||||
|   ... | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### Tokens | ||||
| No specific tokens are available. | ||||
|  | ||||
| ### Input | ||||
| Supported input types and default templates: | ||||
|  | ||||
| #### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| #### Plain (`plain`) | ||||
| Default template: | ||||
| ``` | ||||
| {localpart} | ||||
| {domain} | ||||
| {mxid} | ||||
| ``` | ||||
| ### Output | ||||
| Supported output types and default templates: | ||||
|  | ||||
| #### JSON (`json`) | ||||
| Same as the [REST Identity Store](rest.md); | ||||
|  | ||||
| #### Plain (`plain`) | ||||
| **Not supported at this time.** Use the JSON type. | ||||
| @@ -24,10 +24,13 @@ ldap.connection.host: 'ldapHostnameOrIp' | ||||
| ldap.connection.port: 389 | ||||
| ldap.connection.bindDn: 'CN=My Mxisd User,OU=Users,DC=example,DC=org' | ||||
| ldap.connection.bindPassword: 'TheUserPassword' | ||||
| ldap.connection.baseDn: 'OU=Users,DC=example,DC=org' | ||||
| ldap.connection.baseDNs: | ||||
|   - 'OU=Users,DC=example,DC=org' | ||||
| ``` | ||||
| These are standard LDAP connection configuration. mxisd will try to connect on port default port 389 without encryption. | ||||
|  | ||||
| If you would like to use several Base DNs, simply add more entries under `baseDNs`. | ||||
|  | ||||
| ### TLS/SSL connection | ||||
| If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported): | ||||
| ```yaml | ||||
|   | ||||
| @@ -3,38 +3,45 @@ The REST backend allows you to query identity data in existing webapps, like: | ||||
| - Forums (phpBB, Discourse, etc.) | ||||
| - Custom Identity stores (Keycloak, ...) | ||||
| - CRMs (Wordpress, ...) | ||||
| - self-hosted clouds (Nextcloud, ownCloud, ...) | ||||
| - Self-hosted clouds (Nextcloud, ownCloud, ...) | ||||
|  | ||||
| To integrate this backend with your webapp, you will need to implement three specific REST endpoints detailed below. | ||||
| To integrate this backend with your webapp, you will need to implement the REST endpoints described below. | ||||
|  | ||||
| ## Features | ||||
| | Name                                            | Supported? | | ||||
| |----------------|------------| | ||||
| | Authentication | Yes        | | ||||
| | Directory      | Yes        | | ||||
| | Identity       | Yes        | | ||||
| | Profile        | No         | | ||||
| |-------------------------------------------------|------------| | ||||
| | [Authentication](../features/authentication.md) | Yes        | | ||||
| | [Directory](../features/directory.md)           | Yes        | | ||||
| | [Identity](../features/identity.md)             | Yes        | | ||||
| | [Profile](../features/profile.md)               | Yes        | | ||||
|  | ||||
| ## Configuration | ||||
| | Key                                  | Default                                        | Description                                          | | ||||
| |----------------------------------|------------------------------------------------|------------------------------------------------------| | ||||
| |--------------------------------------|------------------------------------------------|------------------------------------------------------| | ||||
| | `rest.enabled`                       | `false`                                        | Globally enable/disable the REST backend             | | ||||
| | `rest.host`                          | *None*                                         | Default base URL to use for the different endpoints. | | ||||
| | `rest.endpoints.auth`                | `/_mxisd/backend/api/v1/auth/login`            | Validate credentials and get user profile            | | ||||
| | `rest.endpoints.directory`           | `/_mxisd/backend/api/v1/directory/user/search` | Search for users by arbitrary input                  | | ||||
| | `rest.endpoints.identity.single`     | `/_mxisd/backend/api/v1/identity/single`       | Endpoint to query a single 3PID                      | | ||||
| | `rest.endpoints.identity.bulk`       | `/_mxisd/backend/api/v1/identity/bulk`         | Endpoint to query a list of 3PID                     | | ||||
| | `rest.endpoints.profile.displayName` | `/_mxisd/backend/api/v1/profile/displayName`   | Query the display name for a Matrix ID | ||||
| | `rest.endpoints.profile.threepids`   | `/_mxisd/backend/api/v1/profile/threepids`     | Query the 3PIDs for a Matrix ID | ||||
| | `rest.endpoints.profile.roles`       | `/_mxisd/backend/api/v1/profile/roles`         | Query the Roles for a Matrix ID | ||||
|  | ||||
| Endpoint values can handle two formats: | ||||
| - URL Path starting with `/` that gets happened to the `rest.host` | ||||
| - Full URL, if you want each endpoint to go to a specific server/protocol/port | ||||
|  | ||||
| If an endpoint value is configured as an empty string, it will disable that specific feature, essentially bypassing the | ||||
| Identity store for that specific query. | ||||
|  | ||||
| `rest.host` is mandatory if at least one endpoint is not a full URL. | ||||
|  | ||||
| ## Endpoints | ||||
| ### Authentication | ||||
| HTTP method: `POST`   | ||||
| Content-type: JSON UTF-8 | ||||
| - Method: `POST` | ||||
| - Content-Type: `application/json` (JSON) | ||||
| - Encoding: `UTF8` | ||||
|    | ||||
| #### Request Body | ||||
| ```json | ||||
| @@ -87,8 +94,9 @@ If the authentication succeed: | ||||
| ``` | ||||
|  | ||||
| ### Directory | ||||
| HTTP method: `POST` | ||||
| Content-type: JSON UTF-8 | ||||
| - Method: `POST` | ||||
| - Content-Type: `application/json` (JSON) | ||||
| - Encoding: `UTF8` | ||||
|  | ||||
| #### Request Body | ||||
| ```json | ||||
| @@ -113,7 +121,7 @@ If users found: | ||||
|       "user_id": "UserIdLocalpart" | ||||
|     }, | ||||
|     { | ||||
|       ... | ||||
|       "...": "..." | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -129,10 +137,11 @@ If no user found: | ||||
|  | ||||
| ### Identity | ||||
| #### Single 3PID lookup | ||||
| HTTP method: `POST`   | ||||
| Content-type: JSON UTF-8   | ||||
| - Method: `POST` | ||||
| - Content-Type: `application/json` (JSON) | ||||
| - Encoding: `UTF8` | ||||
|    | ||||
| #### Request Body | ||||
| ##### Request Body | ||||
| ```json | ||||
| { | ||||
|   "lookup": { | ||||
| @@ -142,7 +151,7 @@ Content-type: JSON UTF-8 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Response Body | ||||
| ##### Response Body | ||||
| If a match was found: | ||||
| - `lookup.id.type` supported values: `localpart`, `mxid` | ||||
| ```json | ||||
| @@ -164,10 +173,11 @@ If no match was found: | ||||
| ``` | ||||
|  | ||||
| #### Bulk 3PID lookup | ||||
| HTTP method: `POST`   | ||||
| Content-type: JSON UTF-8   | ||||
| - Method: `POST` | ||||
| - Content-Type: `application/json` (JSON) | ||||
| - Encoding: `UTF8` | ||||
|    | ||||
| #### Request Body | ||||
| ##### Request Body | ||||
| ```json | ||||
| { | ||||
|   "lookup": [ | ||||
| @@ -183,7 +193,7 @@ Content-type: JSON UTF-8 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Response Body | ||||
| ##### Response Body | ||||
| For all entries where a match was found: | ||||
| - `lookup[].id.type` supported values: `localpart`, `mxid` | ||||
| ```json | ||||
| @@ -215,3 +225,46 @@ If no match was found: | ||||
|   "lookup": [] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Profile | ||||
| #### Request Body | ||||
| For all requests, the values are the same: | ||||
| - Method: `POST` | ||||
| - Content-Type: `application/json` (JSON) | ||||
| - Encoding: `UTF8` | ||||
|  | ||||
| With body (example values): | ||||
| ##### Request Body | ||||
| ```json | ||||
| { | ||||
|     "mxid": "@john.doe:example.org", | ||||
|     "localpart": "john.doe", | ||||
|     "domain": "example.org" | ||||
| } | ||||
| ``` | ||||
| #### Response Body | ||||
| For all responses, the same object structure will be parsed, making the non-relevant fields as optional. | ||||
|  | ||||
| Structure with example values: | ||||
| ```json | ||||
| { | ||||
|   "profile": { | ||||
|     "display_name": "John Doe", | ||||
|     "threepids": [ | ||||
|       { | ||||
|         "medium": "email", | ||||
|         "address": "john.doe@example.org" | ||||
|       }, | ||||
|       { | ||||
|         "...": "..." | ||||
|       } | ||||
|     ], | ||||
|     "roles": [ | ||||
|       "DomainUsers", | ||||
|       "SalesOrg", | ||||
|       "..." | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| The base `profile` key is mandatory. `display_name`, `threepids` and `roles` are only to be returned on the relevant request. | ||||
|   | ||||
| @@ -18,48 +18,62 @@ | ||||
|  | ||||
| ## Overview | ||||
| When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client, | ||||
| the identity server is called to validate the 3PID. | ||||
| the identity server is contacted to validate the 3PID. | ||||
|  | ||||
| To validate the 3PID the identity server sends a message to the 3PID (e.g. an | ||||
| email) with a hyperlink back to a web-page managed by the identity server to | ||||
| confirm ownership of the 3PID. | ||||
|  | ||||
| Once this 3PID is validated, the Homeserver will publish the user Matrix ID on the Identity Server and | ||||
| add this 3PID to the Matrix account which initiated the request. | ||||
|  | ||||
| ## Purpose | ||||
| This serves two purposes: | ||||
| - Add the 3PID as an administrative/login info for the Homeserver directly | ||||
| - Publish, or *Bind*, the 3PID so it can be queried from Homeservers and clients when inviting someone in a room | ||||
| by a 3PID, allowing it to be resolved to a Matrix ID. | ||||
|  | ||||
| ## Federation | ||||
| Federation is based on the principle that one can get a domain name and serve services and information within that | ||||
| domain namespace in a way which can be discovered following a specific protocol or specification. | ||||
| In a federated set up, identity servers must cooperate to find the Matrix ID associated with a 3PID. | ||||
|  | ||||
| In the Matrix eco-system, some 3PID can be federated (e.g. emails) while some others cannot (phone numbers). | ||||
| Also, Matrix users might add 3PIDs that would not point to the Identity server that actually holds the 3PID binding.   | ||||
| Federation is based on the principle that each server is responsible for its own (dns) domain. | ||||
| Therefore only those 3PID can be federated that can be distinguished by their | ||||
| domain such as email addresses. | ||||
|  | ||||
| Example: a user from Homeserver `example.org` adds an email `john@example.com`.   | ||||
| Federated identity servers would try to find the identity server at `example.com` and ask it for the Matrix ID of associated with `john@example.com`. | ||||
|  | ||||
| Nevertheless, Matrix users might add 3PIDs that are not associated to a domain, for example telephone numbers. | ||||
| Or they might even add 3PIDs associated to a different domain (such as an email address hosted by gmail). | ||||
| Such 3PIDs cannot be resolved in a federated way. | ||||
|  | ||||
| Example: a user from Homeserver `example.org` adds an email `john@gmail.com`.   | ||||
| If a federated lookup was performed, Identity servers would try to find the 3PID bind at the `gmail.com` server, and | ||||
| not `example.org`. | ||||
|  | ||||
| To allow global publishing of 3PID bindings to be found anywhere within the current protocol specification, one would | ||||
| perform a *Remote session* and *Remote bind*, effectively starting a new 3PID session with another Identity server on | ||||
| In order to resolve such 3PIDs, i.e. 3PIDs that cannot be resolved in a Federated way, an identity server can be configured such that | ||||
| - 3PIDs that cannot be resolved locally or using federation, are fowarded to another global identity server. | ||||
| - registration of new 3PIDs that cannot be looked up in a federated fashion, is forwarded to another global identity server. | ||||
|  | ||||
| By forwarding a 3PIDs registration the identity creates a *Remote session* and *Remote bind*, effectively starting a new 3PID session with another Identity server on | ||||
| behalf of the user.  | ||||
|  | ||||
| To ensure lookup works consistency within the current Matrix network, the central Matrix.org Identity Server should be | ||||
| used to store *remote* sessions and binds. | ||||
|  | ||||
| On the flip side, at the time of writing, the Matrix specification and the central Matrix.org servers do not allow to | ||||
| remote a 3PID bind. This means that once a 3PID is published (email, phone number, etc.), it cannot be easily removed | ||||
| However, at the time of writing, the Matrix specification and the central Matrix.org servers do not allow to remote a 3PID bind. | ||||
| This means that once a 3PID is published (email, phone number, etc.), it cannot be easily removed | ||||
| and would require contacting the Matrix.org administrators for each bind individually. | ||||
| This poses a privacy, control and security concern, especially for groups/corporations that want to keep a tight control | ||||
| on where such identifiers can be made publicly visible. | ||||
|  | ||||
| To ensure full control, validation management rely on two concepts: | ||||
| To ensure full control, validation management relies on two concepts: | ||||
| - The scope of 3PID being validated | ||||
| - The scope of 3PID sessions that should be possible/offered | ||||
|  | ||||
| ### 3PID scope | ||||
| 3PID can either be scoped as local or remote. | ||||
|  | ||||
| Local means that they can looked up using federation and that such federation call would end up on the local | ||||
| Local means that they can be looked up using federation and that such a federation call would end up on the local | ||||
| Identity Server.   | ||||
| Remote means that they cannot be lookup using federation or that a federation call would not end up on the local | ||||
| Identity Server. | ||||
| @@ -139,7 +153,7 @@ session.policy.validation.forRemote: | ||||
| `session.policy.validation` is the core configuration to control what users configured to use your Identity server | ||||
| are allowed to do in terms of 3PID sessions. | ||||
|  | ||||
| The policy is divided contains a global on/off switch for 3PID sessions using `.enabled`   | ||||
| The policy has a global on/off switch for 3PID sessions using `.enabled`   | ||||
| It is also divided into two sections: `forLocal` and `forRemote` which refers to the 3PID scopes.   | ||||
|  | ||||
| Each scope is divided into three parts: | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -30,6 +30,10 @@ public class UserID { | ||||
|         // stub for (de)serialization | ||||
|     } | ||||
|  | ||||
|     public UserID(UserIdType type, String value) { | ||||
|         this(type.getId(), value); | ||||
|     } | ||||
|  | ||||
|     public UserID(String type, String value) { | ||||
|         this.type = type; | ||||
|         this.value = value; | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -22,91 +22,190 @@ package io.kamax.mxisd.as; | ||||
|  | ||||
| import com.google.gson.JsonObject; | ||||
| import io.kamax.matrix.MatrixID; | ||||
| import io.kamax.matrix.ThreePidMedium; | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix._ThreePid; | ||||
| import io.kamax.matrix.event.EventKey; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.backend.sql.synapse.Synapse; | ||||
| import io.kamax.mxisd.config.ListenerConfig; | ||||
| import io.kamax.mxisd.config.MatrixConfig; | ||||
| import io.kamax.mxisd.notification.NotificationManager; | ||||
| import io.kamax.mxisd.profile.ProfileManager; | ||||
| import io.kamax.mxisd.storage.IStorage; | ||||
| import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao; | ||||
| import io.kamax.mxisd.util.GsonParser; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.io.InputStream; | ||||
| import java.time.Instant; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Component | ||||
| public class AppServiceHandler { | ||||
|  | ||||
|     private final Logger log = LoggerFactory.getLogger(AppServiceHandler.class); | ||||
|  | ||||
|     private final GsonParser parser; | ||||
|  | ||||
|     private String localpart; | ||||
|     private MatrixConfig cfg; | ||||
|     private IStorage store; | ||||
|     private ProfileManager profiler; | ||||
|     private NotificationManager notif; | ||||
|     private Synapse synapse; | ||||
|  | ||||
|     private Map<String, CompletableFuture<String>> transactionsInProgress; | ||||
|  | ||||
|     @Autowired | ||||
|     public AppServiceHandler(MatrixConfig cfg, ProfileManager profiler, NotificationManager notif, Synapse synapse) { | ||||
|     public AppServiceHandler(ListenerConfig lCfg, MatrixConfig cfg, IStorage store, ProfileManager profiler, NotificationManager notif, Synapse synapse) { | ||||
|         this.cfg = cfg; | ||||
|         this.store = store; | ||||
|         this.profiler = profiler; | ||||
|         this.notif = notif; | ||||
|         this.synapse = synapse; | ||||
|  | ||||
|         localpart = lCfg.getLocalpart(); | ||||
|         parser = new GsonParser(); | ||||
|         transactionsInProgress = new ConcurrentHashMap<>(); | ||||
|     } | ||||
|  | ||||
|     public CompletableFuture<String> processTransaction(String txnId, InputStream is) { | ||||
|         synchronized (this) { | ||||
|             Optional<ASTransactionDao> dao = store.getTransactionResult(localpart, txnId); | ||||
|             if (dao.isPresent()) { | ||||
|                 log.info("AS Transaction {} already processed - returning computed result", txnId); | ||||
|                 return CompletableFuture.completedFuture(dao.get().getResult()); | ||||
|             } | ||||
|  | ||||
|             CompletableFuture<String> f = transactionsInProgress.get(txnId); | ||||
|             if (Objects.nonNull(f)) { | ||||
|                 log.info("Returning future for transaction {}", txnId); | ||||
|                 return f; | ||||
|             } | ||||
|  | ||||
|             transactionsInProgress.put(txnId, new CompletableFuture<>()); | ||||
|         } | ||||
|  | ||||
|         CompletableFuture<String> future = transactionsInProgress.get(txnId); | ||||
|  | ||||
|         Instant start = Instant.now(); | ||||
|         log.info("Processing AS Transaction {}: start", txnId); | ||||
|         try { | ||||
|             List<JsonObject> events = GsonUtil.asList(GsonUtil.getArray(parser.parse(is), "events"), JsonObject.class); | ||||
|             is.close(); | ||||
|             log.debug("{} event(s) parsed", events.size()); | ||||
|  | ||||
|             processTransaction(events); | ||||
|             Instant end = Instant.now(); | ||||
|             log.info("Processed AS transaction {} in {} ms", txnId, (Instant.now().toEpochMilli() - start.toEpochMilli())); | ||||
|  | ||||
|             String result = "{}"; | ||||
|  | ||||
|             try { | ||||
|                 log.info("Saving transaction details to store"); | ||||
|                 store.insertTransactionResult(localpart, txnId, end, result); | ||||
|             } finally { | ||||
|                 log.debug("Removing CompletedFuture from transaction map"); | ||||
|                 transactionsInProgress.remove(txnId); | ||||
|             } | ||||
|  | ||||
|             future.complete(result); | ||||
|         } catch (Exception e) { | ||||
|             log.error("Unable to properly process transaction {}", txnId, e); | ||||
|             future.completeExceptionally(e); | ||||
|         } | ||||
|  | ||||
|         log.info("Processing AS Transaction {}: end", txnId); | ||||
|         return future; | ||||
|     } | ||||
|  | ||||
|     public void processTransaction(List<JsonObject> eventsJson) { | ||||
|         log.info("Processing transaction events: start"); | ||||
|  | ||||
|         eventsJson.forEach(ev -> { | ||||
|             String evId = EventKey.Id.getStringOrNull(ev); | ||||
|             if (StringUtils.isBlank(evId)) { | ||||
|                 log.warn("Event has no ID, skipping"); | ||||
|                 log.debug("Event:\n{}", GsonUtil.getPrettyForLog(ev)); | ||||
|                 return; | ||||
|             } | ||||
|             log.debug("Event {}: processing start", evId); | ||||
|  | ||||
|             String roomId = EventKey.RoomId.getStringOrNull(ev); | ||||
|             if (StringUtils.isBlank(roomId)) { | ||||
|                 log.debug("Event has no room ID, skipping"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             String senderId = EventKey.Sender.getStringOrNull(ev); | ||||
|             if (StringUtils.isBlank(senderId)) { | ||||
|                 log.debug("Event has no sender ID, skipping"); | ||||
|                 return; | ||||
|             } | ||||
|             _MatrixID sender = MatrixID.asAcceptable(senderId); | ||||
|             log.debug("Sender: {}", senderId); | ||||
|  | ||||
|             if (!StringUtils.equals("m.room.member", GsonUtil.getStringOrNull(ev, "type"))) { | ||||
|                 log.debug("This is not a room membership event, skipping"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (!StringUtils.equals("invite", GsonUtil.getStringOrNull(ev, "membership"))) { | ||||
|                 log.debug("This is not an invite event, skipping"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             String roomId = GsonUtil.getStringOrNull(ev, "room_id"); | ||||
|             _MatrixID sender = MatrixID.asAcceptable(GsonUtil.getStringOrNull(ev, "sender")); | ||||
|             EventKey.StateKey.findString(ev).ifPresent(id -> { | ||||
|                 _MatrixID mxid = MatrixID.asAcceptable(id); | ||||
|                 if (!StringUtils.equals(mxid.getDomain(), cfg.getDomain())) { | ||||
|             String inviteeId = EventKey.StateKey.getStringOrNull(ev); | ||||
|             if (StringUtils.isBlank(inviteeId)) { | ||||
|                 log.warn("Invalid event: No invitee ID, skipping"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _MatrixID invitee = MatrixID.asAcceptable(inviteeId); | ||||
|             if (!StringUtils.equals(invitee.getDomain(), cfg.getDomain())) { | ||||
|                 log.debug("Ignoring invite for {}: not a local user"); | ||||
|                 return; | ||||
|             } | ||||
|                 log.info("Got invite for {}", id); | ||||
|  | ||||
|             log.info("Got invite from {} to {}", senderId, inviteeId); | ||||
|  | ||||
|             boolean wasSent = false; | ||||
|                 List<_ThreePid> tpids = profiler.getThreepids(mxid); | ||||
|                 if (tpids.isEmpty()) { | ||||
|                     log.info("No email found in identity stores for {}", id); | ||||
|                 } | ||||
|             List<_ThreePid> tpids = profiler.getThreepids(invitee).stream() | ||||
|                     .filter(tpid -> ThreePidMedium.Email.is(tpid.getMedium())) | ||||
|                     .collect(Collectors.toList()); | ||||
|             log.info("Found {} email(s) in identity store for {}", tpids.size(), inviteeId); | ||||
|  | ||||
|             for (_ThreePid tpid : tpids) { | ||||
|                     if (!StringUtils.equals("email", tpid.getMedium())) { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     log.info("Found an email address to notify about room invitation: {}", tpid.getAddress()); | ||||
|                 log.info("Found Email to notify about room invitation: {}", tpid.getAddress()); | ||||
|                 Map<String, String> properties = new HashMap<>(); | ||||
|                 profiler.getDisplayName(sender).ifPresent(name -> properties.put("sender_display_name", name)); | ||||
|                 try { | ||||
|                     synapse.getRoomName(roomId).ifPresent(name -> properties.put("room_name", name)); | ||||
|                 } catch (RuntimeException e) { | ||||
|                         log.warn("Unable to fetch room name - Did you provide synapse DB information as documented?"); | ||||
|                         log.warn("Underlying error:", e); | ||||
|                     log.warn("Could not fetch room name", e); | ||||
|                     log.info("Unable to fetch room name: Did you integrate your Homeserver as documented?"); | ||||
|                 } | ||||
|  | ||||
|                     IMatrixIdInvite inv = new MatrixIdInvite(roomId, sender, mxid, tpid.getMedium(), tpid.getAddress(), properties); | ||||
|                 IMatrixIdInvite inv = new MatrixIdInvite(roomId, sender, invitee, tpid.getMedium(), tpid.getAddress(), properties); | ||||
|                 notif.sendForInvite(inv); | ||||
|                 log.info("Notification for invite of {} sent to {}", inviteeId, tpid.getAddress()); | ||||
|                 wasSent = true; | ||||
|             } | ||||
|  | ||||
|             log.info("Was notification sent? {}", wasSent); | ||||
|  | ||||
|             log.debug("Event {}: processing end", evId); | ||||
|         }); | ||||
|         }); | ||||
|  | ||||
|         log.info("Processing transaction events: end"); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -20,37 +20,96 @@ | ||||
|  | ||||
| package io.kamax.mxisd.auth; | ||||
|  | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonParser; | ||||
| import com.google.gson.JsonSyntaxException; | ||||
| import com.google.i18n.phonenumbers.NumberParseException; | ||||
| import com.google.i18n.phonenumbers.PhoneNumberUtil; | ||||
| import com.google.i18n.phonenumbers.Phonenumber; | ||||
| import io.kamax.matrix.MatrixID; | ||||
| import io.kamax.matrix.ThreePid; | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix._ThreePid; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.UserIdType; | ||||
| import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||
| import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||
| import io.kamax.mxisd.config.AuthenticationConfig; | ||||
| import io.kamax.mxisd.config.MatrixConfig; | ||||
| import io.kamax.mxisd.dns.ClientDnsOverwrite; | ||||
| import io.kamax.mxisd.exception.RemoteLoginException; | ||||
| import io.kamax.mxisd.invitation.InvitationManager; | ||||
| import io.kamax.mxisd.lookup.ThreePidMapping; | ||||
| import io.kamax.mxisd.lookup.strategy.LookupStrategy; | ||||
| import io.kamax.mxisd.util.RestClientUtils; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.http.HttpEntity; | ||||
| import org.apache.http.client.methods.CloseableHttpResponse; | ||||
| import org.apache.http.client.methods.HttpPost; | ||||
| import org.apache.http.client.utils.URIBuilder; | ||||
| import org.apache.http.impl.client.CloseableHttpClient; | ||||
| import org.apache.http.util.EntityUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.net.URI; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
|  | ||||
| @Service | ||||
| public class AuthManager { | ||||
|  | ||||
|     private Logger log = LoggerFactory.getLogger(AuthManager.class); | ||||
|     private static final String TypeKey = "type"; | ||||
|     private static final String UserKey = "user"; | ||||
|     private static final String IdentifierKey = "identifier"; | ||||
|     private static final String ThreepidMediumKey = "medium"; | ||||
|     private static final String ThreepidAddressKey = "address"; | ||||
|     private static final String UserIdTypeValue = "m.id.user"; | ||||
|     private static final String ThreepidTypeValue = "m.id.thirdparty"; | ||||
|  | ||||
|     @Autowired | ||||
|     private List<AuthenticatorProvider> providers = new ArrayList<>(); | ||||
|     private final Logger log = LoggerFactory.getLogger(AuthManager.class); | ||||
|     private final Gson gson = GsonUtil.get(); | ||||
|  | ||||
|     @Autowired | ||||
|     private List<AuthenticatorProvider> providers; | ||||
|     private MatrixConfig mxCfg; | ||||
|     private AuthenticationConfig cfg; | ||||
|     private InvitationManager invMgr; | ||||
|     private ClientDnsOverwrite dns; | ||||
|     private LookupStrategy strategy; | ||||
|     private CloseableHttpClient client; | ||||
|  | ||||
|     @Autowired | ||||
|     private InvitationManager invMgr; | ||||
|     public AuthManager( | ||||
|             AuthenticationConfig cfg, | ||||
|             MatrixConfig mxCfg, | ||||
|             List<AuthenticatorProvider> providers, | ||||
|             LookupStrategy strategy, | ||||
|             InvitationManager invMgr, | ||||
|             ClientDnsOverwrite dns, | ||||
|             CloseableHttpClient client | ||||
|     ) { | ||||
|         this.cfg = cfg; | ||||
|         this.mxCfg = mxCfg; | ||||
|         this.providers = new ArrayList<>(providers); | ||||
|         this.strategy = strategy; | ||||
|         this.invMgr = invMgr; | ||||
|         this.dns = dns; | ||||
|         this.client = client; | ||||
|     } | ||||
|  | ||||
|     public String resolveProxyUrl(URI target) { | ||||
|         URIBuilder builder = dns.transform(target); | ||||
|         String urlToLogin = builder.toString(); | ||||
|         log.info("Proxy resolution: {} to {}", target.toString(), urlToLogin); | ||||
|         return urlToLogin; | ||||
|     } | ||||
|  | ||||
|     public UserAuthResult authenticate(String id, String password) { | ||||
|         _MatrixID mxid = MatrixID.asAcceptable(id); | ||||
| @@ -59,9 +118,10 @@ public class AuthManager { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             log.info("Attempting authentication with store {}", provider.getClass().getSimpleName()); | ||||
|  | ||||
|             BackendAuthResult result = provider.authenticate(mxid, password); | ||||
|             if (result.isSuccess()) { | ||||
|  | ||||
|                 String mxId; | ||||
|                 if (UserIdType.Localpart.is(result.getId().getType())) { | ||||
|                     mxId = MatrixID.from(result.getId().getValue(), mxCfg.getDomain()).acceptable().getId(); | ||||
| @@ -91,4 +151,128 @@ public class AuthManager { | ||||
|         return new UserAuthResult().failure(); | ||||
|     } | ||||
|  | ||||
|     public String proxyLogin(URI target, String body) { | ||||
|         JsonObject reqJsonObject = io.kamax.matrix.json.GsonUtil.parseObj(body); | ||||
|  | ||||
|         GsonUtil.findObj(reqJsonObject, IdentifierKey).ifPresent(obj -> { | ||||
|             GsonUtil.findString(obj, TypeKey).ifPresent(type -> { | ||||
|                 if (StringUtils.equals(type, UserIdTypeValue)) { | ||||
|                     log.info("Login request is User ID type"); | ||||
|  | ||||
|                     if (cfg.getRewrite().getUser().getRules().isEmpty()) { | ||||
|                         log.info("No User ID rewrite rules to apply"); | ||||
|                     } else { | ||||
|                         log.info("User ID rewrite rules: checking for a match"); | ||||
|  | ||||
|                         String userId = GsonUtil.getStringOrThrow(obj, UserKey); | ||||
|                         for (AuthenticationConfig.Rule m : cfg.getRewrite().getUser().getRules()) { | ||||
|                             if (m.getPattern().matcher(userId).matches()) { | ||||
|                                 log.info("Found matching pattern, resolving to 3PID with medium {}", m.getMedium()); | ||||
|  | ||||
|                                 // Remove deprecated login info on the top object if exists to avoid duplication | ||||
|                                 reqJsonObject.remove(UserKey); | ||||
|                                 obj.addProperty(TypeKey, ThreepidTypeValue); | ||||
|                                 obj.addProperty(ThreepidMediumKey, m.getMedium()); | ||||
|                                 obj.addProperty(ThreepidAddressKey, userId); | ||||
|  | ||||
|                                 log.info("Rewrite to 3PID done"); | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         log.info("User ID rewrite rules: done checking rules"); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         GsonUtil.findObj(reqJsonObject, IdentifierKey).ifPresent(obj -> { | ||||
|             GsonUtil.findString(obj, TypeKey).ifPresent(type -> { | ||||
|                 if (StringUtils.equals(type, ThreepidTypeValue)) { | ||||
|                     // Remove deprecated login info if exists to avoid duplication | ||||
|                     reqJsonObject.remove(ThreepidMediumKey); | ||||
|                     reqJsonObject.remove(ThreepidAddressKey); | ||||
|  | ||||
|                     GsonUtil.findPrimitive(obj, ThreepidMediumKey).ifPresent(medium -> { | ||||
|                         GsonUtil.findPrimitive(obj, ThreepidAddressKey).ifPresent(address -> { | ||||
|                             log.info("Login request with medium '{}' and address '{}'", medium.getAsString(), address.getAsString()); | ||||
|                             strategy.findLocal(medium.getAsString(), address.getAsString()).ifPresent(lookupDataOpt -> { | ||||
|                                 obj.remove(ThreepidMediumKey); | ||||
|                                 obj.remove(ThreepidAddressKey); | ||||
|                                 obj.addProperty(TypeKey, UserIdTypeValue); | ||||
|                                 obj.addProperty(UserKey, lookupDataOpt.getMxid().getLocalPart()); | ||||
|                             }); | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (StringUtils.equals(type, "m.id.phone")) { | ||||
|                     // Remove deprecated login info if exists to avoid duplication | ||||
|                     reqJsonObject.remove(ThreepidMediumKey); | ||||
|                     reqJsonObject.remove(ThreepidAddressKey); | ||||
|  | ||||
|                     GsonUtil.findPrimitive(obj, "number").ifPresent(number -> { | ||||
|                         GsonUtil.findPrimitive(obj, "country").ifPresent(country -> { | ||||
|                             log.info("Login request with phone '{}'-'{}'", country.getAsString(), number.getAsString()); | ||||
|                             try { | ||||
|                                 PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); | ||||
|                                 Phonenumber.PhoneNumber phoneNumber = phoneUtil.parse(number.getAsString(), country.getAsString()); | ||||
|                                 String msisdn = phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164).replace("+", ""); | ||||
|                                 String medium = "msisdn"; | ||||
|                                 strategy.findLocal(medium, msisdn).ifPresent(lookupDataOpt -> { | ||||
|                                     obj.remove("country"); | ||||
|                                     obj.remove("number"); | ||||
|                                     obj.addProperty(TypeKey, UserIdTypeValue); | ||||
|                                     obj.addProperty(UserKey, lookupDataOpt.getMxid().getLocalPart()); | ||||
|                                 }); | ||||
|                             } catch (NumberParseException e) { | ||||
|                                 log.error("Not a valid phone number"); | ||||
|                                 throw new RuntimeException(e); | ||||
|                             } | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         // invoke 'login' on homeserver | ||||
|         HttpPost httpPost = RestClientUtils.post(resolveProxyUrl(target), gson, reqJsonObject); | ||||
|         try (CloseableHttpResponse httpResponse = client.execute(httpPost)) { | ||||
|             // check http status | ||||
|             int status = httpResponse.getStatusLine().getStatusCode(); | ||||
|             log.info("http status = {}", status); | ||||
|             if (status != 200) { | ||||
|                 // try to get possible json error message from response | ||||
|                 // otherwise just get returned plain error message | ||||
|                 String errcode = String.valueOf(httpResponse.getStatusLine().getStatusCode()); | ||||
|                 String error = EntityUtils.toString(httpResponse.getEntity()); | ||||
|                 if (httpResponse.getEntity() != null) { | ||||
|                     try { | ||||
|                         JsonObject bodyJson = new JsonParser().parse(error).getAsJsonObject(); | ||||
|                         if (bodyJson.has("errcode")) { | ||||
|                             errcode = bodyJson.get("errcode").getAsString(); | ||||
|                         } | ||||
|                         if (bodyJson.has("error")) { | ||||
|                             error = bodyJson.get("error").getAsString(); | ||||
|                         } | ||||
|                         throw new RemoteLoginException(status, errcode, error, bodyJson); | ||||
|                     } catch (JsonSyntaxException e) { | ||||
|                         log.warn("Response body is not JSON"); | ||||
|                     } | ||||
|                 } | ||||
|                 throw new RemoteLoginException(status, errcode, error); | ||||
|             } | ||||
|  | ||||
|             // return response | ||||
|             HttpEntity entity = httpResponse.getEntity(); | ||||
|             if (Objects.isNull(entity)) { | ||||
|                 log.warn("Expected HS to return data but got nothing"); | ||||
|                 return ""; | ||||
|             } else { | ||||
|                 return IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8); | ||||
|             } | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -38,6 +38,10 @@ public class BackendAuthResult { | ||||
|             return displayName; | ||||
|         } | ||||
|  | ||||
|         public void setDisplayName(String displayName) { | ||||
|             this.displayName = displayName; | ||||
|         } | ||||
|  | ||||
|         public Set<ThreePid> getThreePids() { | ||||
|             return threePids; | ||||
|         } | ||||
| @@ -73,6 +77,10 @@ public class BackendAuthResult { | ||||
|     private UserID id; | ||||
|     private BackendAuthProfile profile = new BackendAuthProfile(); | ||||
|  | ||||
|     public void setSuccess(boolean success) { | ||||
|         this.success = success; | ||||
|     } | ||||
|  | ||||
|     public Boolean isSuccess() { | ||||
|         return success; | ||||
|     } | ||||
| @@ -81,6 +89,10 @@ public class BackendAuthResult { | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     public void setId(UserID id) { | ||||
|         this.id = id; | ||||
|     } | ||||
|  | ||||
|     public BackendAuthProfile getProfile() { | ||||
|         return profile; | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,37 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||
|  | ||||
| public class ExecAuthResult extends BackendAuthResult { | ||||
|  | ||||
|     private int exitStatus; | ||||
|  | ||||
|     public int getExitStatus() { | ||||
|         return exitStatus; | ||||
|     } | ||||
|  | ||||
|     public void setExitStatus(int exitStatus) { | ||||
|         this.exitStatus = exitStatus; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										129
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecAuthStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecAuthStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonPrimitive; | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.UserID; | ||||
| import io.kamax.mxisd.UserIdType; | ||||
| import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||
| import io.kamax.mxisd.backend.rest.RestAuthRequestJson; | ||||
| import io.kamax.mxisd.config.ExecConfig; | ||||
| import io.kamax.mxisd.exception.InternalServerError; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
|  | ||||
| @Component | ||||
| public class ExecAuthStore extends ExecStore implements AuthenticatorProvider { | ||||
|  | ||||
|     private final Logger log = LoggerFactory.getLogger(ExecAuthStore.class); | ||||
|  | ||||
|     private ExecConfig.Auth cfg; | ||||
|  | ||||
|     @Autowired | ||||
|     public ExecAuthStore(ExecConfig cfg) { | ||||
|         this.cfg = Objects.requireNonNull(cfg.getAuth()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnabled() { | ||||
|         return cfg.isEnabled(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExecAuthResult authenticate(_MatrixID uId, String password) { | ||||
|         Objects.requireNonNull(uId); | ||||
|         Objects.requireNonNull(password); | ||||
|  | ||||
|         log.info("Performing authentication for {}", uId.getId()); | ||||
|  | ||||
|         ExecAuthResult result = new ExecAuthResult(); | ||||
|         result.setId(new UserID(UserIdType.Localpart, uId.getLocalPart())); | ||||
|  | ||||
|         Processor<ExecAuthResult> p = new Processor<>(cfg); | ||||
|  | ||||
|         p.addTokenMapper(cfg.getToken().getLocalpart(), uId::getLocalPart); | ||||
|         p.addTokenMapper(cfg.getToken().getDomain(), uId::getDomain); | ||||
|         p.addTokenMapper(cfg.getToken().getMxid(), uId::getId); | ||||
|         p.addTokenMapper(cfg.getToken().getPassword(), () -> password); | ||||
|  | ||||
|         p.addJsonInputTemplate(tokens -> { | ||||
|             RestAuthRequestJson json = new RestAuthRequestJson(); | ||||
|             json.setLocalpart(tokens.getLocalpart()); | ||||
|             json.setDomain(tokens.getDomain()); | ||||
|             json.setMxid(tokens.getMxid()); | ||||
|             json.setPassword(tokens.getPassword()); | ||||
|             return json; | ||||
|         }); | ||||
|         p.addInputTemplate(PlainType, tokens -> tokens.getLocalpart() + System.lineSeparator() + | ||||
|                 tokens.getDomain() + System.lineSeparator() + | ||||
|                 tokens.getMxid() + System.lineSeparator() + | ||||
|                 tokens.getPassword() + System.lineSeparator() | ||||
|         ); | ||||
|  | ||||
|         p.withExitHandler(pr -> result.setExitStatus(pr.getExitValue())); | ||||
|  | ||||
|         p.withSuccessHandler(pr -> result.setSuccess(true)); | ||||
|         p.withSuccessDefault(o -> result); | ||||
|         p.addSuccessMapper(JsonType, output -> { | ||||
|             JsonObject data = GsonUtil.getObj(GsonUtil.parseObj(output), "auth"); | ||||
|             GsonUtil.findPrimitive(data, "success") | ||||
|                     .map(JsonPrimitive::getAsBoolean) | ||||
|                     .ifPresent(result::setSuccess); | ||||
|             GsonUtil.findObj(data, "profile") | ||||
|                     .flatMap(profile -> GsonUtil.findString(profile, "display_name")) | ||||
|                     .ifPresent(v -> result.getProfile().setDisplayName(v)); | ||||
|  | ||||
|             return result; | ||||
|         }); | ||||
|         p.addSuccessMapper(PlainType, output -> { | ||||
|             String[] lines = output.split("\\R"); | ||||
|             if (lines.length > 2) { | ||||
|                 throw new InternalServerError("Exec auth command returned more than 2 lines (" + lines.length + ")"); | ||||
|             } | ||||
|  | ||||
|             result.setSuccess(Optional.ofNullable(StringUtils.isEmpty(lines[0]) ? null : lines[0]) | ||||
|                     .map(v -> StringUtils.equalsAnyIgnoreCase(v, "true", "1")) | ||||
|                     .orElse(result.isSuccess())); | ||||
|  | ||||
|             if (lines.length == 2) { | ||||
|                 Optional.ofNullable(StringUtils.isEmpty(lines[1]) ? null : lines[1]) | ||||
|                         .ifPresent(v -> result.getProfile().setDisplayName(v)); | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         }); | ||||
|  | ||||
|         p.withFailureHandler(pr -> result.setSuccess(false)); | ||||
|         p.withFailureDefault(o -> result); | ||||
|  | ||||
|         return p.execute(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,94 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import io.kamax.matrix.MatrixID; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.config.ExecConfig; | ||||
| import io.kamax.mxisd.config.MatrixConfig; | ||||
| import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest; | ||||
| import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; | ||||
| import io.kamax.mxisd.directory.IDirectoryProvider; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| @Component | ||||
| public class ExecDirectoryStore extends ExecStore implements IDirectoryProvider { | ||||
|  | ||||
|     private ExecConfig.Directory cfg; | ||||
|     private MatrixConfig mxCfg; | ||||
|  | ||||
|     @Autowired | ||||
|     public ExecDirectoryStore(ExecConfig cfg, MatrixConfig mxCfg) { | ||||
|         this(cfg.getDirectory(), mxCfg); | ||||
|     } | ||||
|  | ||||
|     public ExecDirectoryStore(ExecConfig.Directory cfg, MatrixConfig mxCfg) { | ||||
|         this.cfg = cfg; | ||||
|         this.mxCfg = mxCfg; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnabled() { | ||||
|         return cfg.isEnabled(); | ||||
|     } | ||||
|  | ||||
|     private UserDirectorySearchResult search(ExecConfig.Process cfg, UserDirectorySearchRequest request) { | ||||
|         if (StringUtils.isEmpty(cfg.getCommand())) { | ||||
|             return UserDirectorySearchResult.empty(); | ||||
|         } | ||||
|  | ||||
|         Processor<UserDirectorySearchResult> p = new Processor<>(cfg); | ||||
|  | ||||
|         p.addJsonInputTemplate(tokens -> new UserDirectorySearchRequest(tokens.getType(), tokens.getQuery())); | ||||
|         p.addInputTemplate(PlainType, tokens -> tokens.getType() + System.lineSeparator() + tokens.getQuery()); | ||||
|  | ||||
|         p.addTokenMapper(cfg.getToken().getType(), request::getBy); | ||||
|         p.addTokenMapper(cfg.getToken().getQuery(), request::getSearchTerm); | ||||
|  | ||||
|         p.addSuccessMapper(JsonType, output -> { | ||||
|             if (StringUtils.isBlank(output)) { | ||||
|                 return UserDirectorySearchResult.empty(); | ||||
|             } | ||||
|  | ||||
|             UserDirectorySearchResult response = GsonUtil.get().fromJson(output, UserDirectorySearchResult.class); | ||||
|             for (UserDirectorySearchResult.Result result : response.getResults()) { | ||||
|                 result.setUserId(MatrixID.asAcceptable(result.getUserId(), mxCfg.getDomain()).getId()); | ||||
|             } | ||||
|             return response; | ||||
|         }); | ||||
|         p.withFailureDefault(output -> new UserDirectorySearchResult()); | ||||
|  | ||||
|         return p.execute(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserDirectorySearchResult searchByDisplayName(String query) { | ||||
|         return search(cfg.getSearch().getByName(), new UserDirectorySearchRequest("name", query)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserDirectorySearchResult searchBy3pid(String query) { | ||||
|         return search(cfg.getSearch().getByName(), new UserDirectorySearchRequest("threepid", query)); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										209
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecIdentityStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecIdentityStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import com.google.gson.JsonArray; | ||||
| import com.google.gson.JsonParseException; | ||||
| import io.kamax.matrix.MatrixID; | ||||
| import io.kamax.matrix.ThreePid; | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.UserID; | ||||
| import io.kamax.mxisd.UserIdType; | ||||
| import io.kamax.mxisd.backend.rest.LookupBulkResponseJson; | ||||
| import io.kamax.mxisd.backend.rest.LookupSingleResponseJson; | ||||
| import io.kamax.mxisd.config.ExecConfig; | ||||
| import io.kamax.mxisd.config.MatrixConfig; | ||||
| import io.kamax.mxisd.exception.InternalServerError; | ||||
| import io.kamax.mxisd.lookup.SingleLookupReply; | ||||
| import io.kamax.mxisd.lookup.SingleLookupRequest; | ||||
| import io.kamax.mxisd.lookup.ThreePidMapping; | ||||
| import io.kamax.mxisd.lookup.provider.IThreePidProvider; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Component | ||||
| public class ExecIdentityStore extends ExecStore implements IThreePidProvider { | ||||
|  | ||||
|     private final Logger log = LoggerFactory.getLogger(ExecIdentityStore.class); | ||||
|  | ||||
|     private final ExecConfig.Identity cfg; | ||||
|     private final MatrixConfig mxCfg; | ||||
|  | ||||
|     @Autowired | ||||
|     public ExecIdentityStore(ExecConfig cfg, MatrixConfig mxCfg) { | ||||
|         this(cfg.getIdentity(), mxCfg); | ||||
|     } | ||||
|  | ||||
|     public ExecIdentityStore(ExecConfig.Identity cfg, MatrixConfig mxCfg) { | ||||
|         this.cfg = cfg; | ||||
|         this.mxCfg = mxCfg; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnabled() { | ||||
|         return cfg.isEnabled(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isLocal() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getPriority() { | ||||
|         return cfg.getPriority(); | ||||
|     } | ||||
|  | ||||
|     private ExecConfig.Process getSingleCfg() { | ||||
|         return cfg.getLookup().getSingle(); | ||||
|     } | ||||
|  | ||||
|     private _MatrixID getUserId(UserID id) { | ||||
|         if (Objects.isNull(id)) { | ||||
|             throw new JsonParseException("User id key is not present"); | ||||
|         } | ||||
|  | ||||
|         if (UserIdType.Localpart.is(id.getType())) { | ||||
|             return MatrixID.asAcceptable(id.getValue(), mxCfg.getDomain()); | ||||
|         } | ||||
|  | ||||
|         if (UserIdType.MatrixID.is(id.getType())) { | ||||
|             return MatrixID.asAcceptable(id.getValue()); | ||||
|         } | ||||
|  | ||||
|         throw new InternalServerError("Unknown user type: " + id.getType()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||
|         Processor<Optional<SingleLookupReply>> p = new Processor<>(); | ||||
|         p.withConfig(cfg.getLookup().getSingle()); | ||||
|  | ||||
|         p.addTokenMapper(getSingleCfg().getToken().getMedium(), request::getType); | ||||
|         p.addTokenMapper(getSingleCfg().getToken().getAddress(), request::getThreePid); | ||||
|  | ||||
|         p.addJsonInputTemplate(tokens -> new ThreePid(tokens.getMedium(), tokens.getAddress())); | ||||
|         p.addInputTemplate(PlainType, tokens -> tokens.getMedium() | ||||
|                 + System.lineSeparator() | ||||
|                 + tokens.getAddress() | ||||
|         ); | ||||
|  | ||||
|         p.addSuccessMapper(JsonType, output -> { | ||||
|             if (StringUtils.isBlank(output)) { | ||||
|                 return Optional.empty(); | ||||
|             } | ||||
|  | ||||
|             return GsonUtil.findObj(GsonUtil.parseObj(output), "lookup") | ||||
|                     .filter(obj -> !obj.entrySet().isEmpty()) | ||||
|                     .map(json -> GsonUtil.get().fromJson(json, LookupSingleResponseJson.class)) | ||||
|                     .map(lookup -> getUserId(lookup.getId())) | ||||
|                     .map(mxId -> new SingleLookupReply(request, mxId)); | ||||
|         }); | ||||
|  | ||||
|         p.addSuccessMapper(PlainType, output -> { | ||||
|             String[] lines = output.split("\\R"); | ||||
|             if (lines.length > 2) { | ||||
|                 throw new InternalServerError("Exec auth command returned more than 2 lines (" + lines.length + ")"); | ||||
|             } | ||||
|  | ||||
|             if (lines.length == 1 && StringUtils.isBlank(lines[0])) { | ||||
|                 return Optional.empty(); | ||||
|             } | ||||
|  | ||||
|             String type = StringUtils.trimToEmpty(lines.length == 1 ? UserIdType.Localpart.getId() : lines[0]); | ||||
|             String value = StringUtils.trimToEmpty(lines.length == 2 ? lines[1] : lines[0]); | ||||
|  | ||||
|             if (UserIdType.Localpart.is(type)) { | ||||
|                 return Optional.of(new SingleLookupReply(request, MatrixID.asAcceptable(value, mxCfg.getDomain()))); | ||||
|             } | ||||
|  | ||||
|             if (UserIdType.MatrixID.is(type)) { | ||||
|                 return Optional.of(new SingleLookupReply(request, MatrixID.asAcceptable(value))); | ||||
|             } | ||||
|  | ||||
|             throw new InternalServerError("Invalid user type: " + type); | ||||
|         }); | ||||
|  | ||||
|         p.withFailureDefault(o -> Optional.empty()); | ||||
|  | ||||
|         return p.execute(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||
|         Processor<List<ThreePidMapping>> p = new Processor<>(); | ||||
|         p.withConfig(cfg.getLookup().getBulk()); | ||||
|  | ||||
|         p.addInput(JsonType, () -> { | ||||
|             JsonArray tpids = GsonUtil.asArray(mappings.stream() | ||||
|                     .map(mapping -> GsonUtil.get().toJsonTree(new ThreePid(mapping.getMedium(), mapping.getValue()))) | ||||
|                     .collect(Collectors.toList())); | ||||
|             return GsonUtil.get().toJson(GsonUtil.makeObj("lookup", tpids)); | ||||
|         }); | ||||
|         p.addInput(PlainType, () -> { | ||||
|             StringBuilder input = new StringBuilder(); | ||||
|             for (ThreePidMapping mapping : mappings) { | ||||
|                 input.append(mapping.getMedium()).append("\t").append(mapping.getValue()).append(System.lineSeparator()); | ||||
|             } | ||||
|             return input.toString(); | ||||
|         }); | ||||
|  | ||||
|         p.addSuccessMapper(JsonType, output -> { | ||||
|             if (StringUtils.isBlank(output)) { | ||||
|                 return Collections.emptyList(); | ||||
|             } | ||||
|  | ||||
|             LookupBulkResponseJson response = GsonUtil.get().fromJson(output, LookupBulkResponseJson.class); | ||||
|             return response.getLookup().stream().map(item -> { | ||||
|                 ThreePidMapping mapping = new ThreePidMapping(); | ||||
|                 mapping.setMedium(item.getMedium()); | ||||
|                 mapping.setValue(item.getAddress()); | ||||
|  | ||||
|                 if (UserIdType.Localpart.is(item.getId().getType())) { | ||||
|                     mapping.setValue(MatrixID.asAcceptable(item.getId().getValue(), mxCfg.getDomain()).getId()); | ||||
|                     return mapping; | ||||
|                 } | ||||
|  | ||||
|                 if (UserIdType.MatrixID.is(item.getId().getType())) { | ||||
|                     mapping.setValue(MatrixID.asAcceptable(item.getId().getValue()).getId()); | ||||
|                     return mapping; | ||||
|                 } | ||||
|  | ||||
|                 throw new InternalServerError("Invalid user type: " + item.getId().getType()); | ||||
|             }).collect(Collectors.toList()); | ||||
|         }); | ||||
|  | ||||
|         p.withFailureDefault(output -> Collections.emptyList()); | ||||
|  | ||||
|         return p.execute(); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										103
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecProfileStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecProfileStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix._ThreePid; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.config.ExecConfig; | ||||
| import io.kamax.mxisd.profile.JsonProfileRequest; | ||||
| import io.kamax.mxisd.profile.JsonProfileResult; | ||||
| import io.kamax.mxisd.profile.ProfileProvider; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Optional; | ||||
|  | ||||
| @Component | ||||
| public class ExecProfileStore extends ExecStore implements ProfileProvider { | ||||
|  | ||||
|     private ExecConfig.Profile cfg; | ||||
|  | ||||
|     @Autowired | ||||
|     public ExecProfileStore(ExecConfig cfg) { | ||||
|         this(cfg.getProfile()); | ||||
|     } | ||||
|  | ||||
|     public ExecProfileStore(ExecConfig.Profile cfg) { | ||||
|         this.cfg = cfg; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnabled() { | ||||
|         return cfg.isEnabled(); | ||||
|     } | ||||
|  | ||||
|     private Optional<JsonProfileResult> getFull(_MatrixID userId, ExecConfig.Process cfg) { | ||||
|         Processor<Optional<JsonProfileResult>> p = new Processor<>(cfg); | ||||
|  | ||||
|         p.addJsonInputTemplate(tokens -> new JsonProfileRequest(tokens.getLocalpart(), tokens.getDomain(), tokens.getMxid())); | ||||
|         p.addInputTemplate(PlainType, tokens -> tokens.getLocalpart() + System.lineSeparator() | ||||
|                 + tokens.getDomain() + System.lineSeparator() | ||||
|                 + tokens.getMxid() + System.lineSeparator() | ||||
|         ); | ||||
|  | ||||
|         p.addTokenMapper(cfg.getToken().getLocalpart(), userId::getLocalPart); | ||||
|         p.addTokenMapper(cfg.getToken().getDomain(), userId::getDomain); | ||||
|         p.addTokenMapper(cfg.getToken().getMxid(), userId::getId); | ||||
|  | ||||
|         p.withFailureDefault(v -> Optional.empty()); | ||||
|  | ||||
|         p.addSuccessMapper(JsonType, output -> { | ||||
|             if (StringUtils.isBlank(output)) { | ||||
|                 return Optional.empty(); | ||||
|             } | ||||
|  | ||||
|             return GsonUtil.findObj(GsonUtil.parseObj(output), "profile") | ||||
|                     .map(obj -> GsonUtil.get().fromJson(obj, JsonProfileResult.class)); | ||||
|         }); | ||||
|  | ||||
|         return p.execute(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<String> getDisplayName(_MatrixID userId) { | ||||
|         return getFull(userId, cfg.getDisplayName()).map(JsonProfileResult::getDisplayName); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<_ThreePid> getThreepids(_MatrixID userId) { | ||||
|         return getFull(userId, cfg.getThreePid()) | ||||
|                 .map(p -> Collections.<_ThreePid>unmodifiableList(p.getThreepids())) | ||||
|                 .orElseGet(Collections::emptyList); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> getRoles(_MatrixID userId) { | ||||
|         return getFull(userId, cfg.getRole()) | ||||
|                 .map(JsonProfileResult::getRoles) | ||||
|                 .orElseGet(Collections::emptyList); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										252
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.exec; | ||||
|  | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.config.ExecConfig; | ||||
| import io.kamax.mxisd.exception.InternalServerError; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.zeroturnaround.exec.ProcessExecutor; | ||||
| import org.zeroturnaround.exec.ProcessResult; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Supplier; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| public class ExecStore { | ||||
|  | ||||
|     public static final String JsonType = "json"; | ||||
|     public static final String PlainType = "plain"; | ||||
|  | ||||
|     protected static String toJson(Object o) { | ||||
|         return GsonUtil.get().toJson(o); | ||||
|     } | ||||
|  | ||||
|     private final Logger log = LoggerFactory.getLogger(ExecStore.class); | ||||
|  | ||||
|     private Supplier<ProcessExecutor> executorSupplier = () -> new ProcessExecutor().readOutput(true); | ||||
|  | ||||
|     public void setExecutorSupplier(Supplier<ProcessExecutor> supplier) { | ||||
|         executorSupplier = supplier; | ||||
|     } | ||||
|  | ||||
|     public class Processor<V> { | ||||
|  | ||||
|         private ExecConfig.Process cfg; | ||||
|  | ||||
|         private Supplier<Optional<String>> inputSupplier; | ||||
|         private Function<String, String> inputTypeMapper; | ||||
|         private Function<String, String> inputUnknownTypeMapper; | ||||
|         private Map<String, Supplier<String>> inputTypeSuppliers; | ||||
|  | ||||
|         private Map<String, Function<ExecConfig.TokenOverride, String>> inputTypeTemplates; | ||||
|         private Supplier<String> inputTypeNoTemplateHandler; | ||||
|         private Map<String, Supplier<String>> tokenMappers; | ||||
|         private Function<String, String> tokenHandler; | ||||
|  | ||||
|         private Consumer<ProcessResult> onExitHandler; | ||||
|         private Consumer<ProcessResult> successHandler; | ||||
|         private Map<String, Function<String, V>> successMappers; | ||||
|         private Function<String, V> successDefault; | ||||
|         private Consumer<ProcessResult> failureHandler; | ||||
|         private Map<String, Function<String, V>> failureMappers; | ||||
|         private Function<String, V> failureDefault; | ||||
|         private Consumer<ProcessResult> unknownHandler; | ||||
|         private Map<String, Function<String, V>> unknownMappers; | ||||
|         private Function<String, V> unknownDefault; | ||||
|  | ||||
|         public Processor(ExecConfig.Process cfg) { | ||||
|             this(); | ||||
|             withConfig(cfg); | ||||
|         } | ||||
|  | ||||
|         public Processor() { | ||||
|             tokenMappers = new HashMap<>(); | ||||
|             inputTypeSuppliers = new HashMap<>(); | ||||
|             inputTypeTemplates = new HashMap<>(); | ||||
|  | ||||
|             withTokenHandler(tokenHandler = input -> { | ||||
|                 for (Map.Entry<String, Supplier<String>> entry : tokenMappers.entrySet()) { | ||||
|                     input = input.replace(entry.getKey(), entry.getValue().get()); | ||||
|                 } | ||||
|                 return input; | ||||
|             }); | ||||
|  | ||||
|             inputTypeNoTemplateHandler = () -> cfg.getInput().getType() | ||||
|                     .map(type -> inputTypeTemplates.get(type).apply(cfg.getToken())) | ||||
|                     .orElse(""); | ||||
|  | ||||
|             inputUnknownTypeMapper = type -> tokenHandler.apply(cfg.getInput().getTemplate().orElseGet(inputTypeNoTemplateHandler)); | ||||
|  | ||||
|             inputTypeMapper = type -> { | ||||
|                 if (!inputTypeSuppliers.containsKey(type)) { | ||||
|                     return inputUnknownTypeMapper.apply(type); | ||||
|                 } | ||||
|  | ||||
|                 return inputTypeSuppliers.get(type).get(); | ||||
|             }; | ||||
|  | ||||
|             inputSupplier = () -> cfg.getInput().getType().map(type -> inputTypeMapper.apply(type)); | ||||
|  | ||||
|             withExitHandler(pr -> { | ||||
|             }); | ||||
|  | ||||
|             successHandler = pr -> { | ||||
|             }; | ||||
|             successMappers = new HashMap<>(); | ||||
|             successDefault = output -> { | ||||
|                 log.info("{} stdout: {}{}", cfg.getCommand(), System.lineSeparator(), output); | ||||
|                 throw new InternalServerError("Exec command has no success handler configured. This is a bug. Please report."); | ||||
|             }; | ||||
|  | ||||
|             failureHandler = pr -> { | ||||
|             }; | ||||
|             failureMappers = new HashMap<>(); | ||||
|             failureDefault = output -> { | ||||
|                 log.info("{} stdout: {}{}", cfg.getCommand(), System.lineSeparator(), output); | ||||
|                 throw new InternalServerError("Exec command has no failure handler configured. This is a bug. Please report."); | ||||
|             }; | ||||
|  | ||||
|             unknownHandler = pr -> log.warn("Unexpected exit status: {}", pr.getExitValue()); | ||||
|             unknownMappers = new HashMap<>(); | ||||
|             withUnknownDefault(output -> { | ||||
|                 log.error("{} stdout:{}{}", cfg.getCommand(), System.lineSeparator(), output); | ||||
|                 throw new InternalServerError("Exec command returned with unexpected exit status"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         public void withConfig(ExecConfig.Process cfg) { | ||||
|             this.cfg = cfg; | ||||
|         } | ||||
|  | ||||
|         public void addTokenMapper(String token, Supplier<String> data) { | ||||
|             tokenMappers.put(token, data); | ||||
|         } | ||||
|  | ||||
|         public void withTokenHandler(Function<String, String> handler) { | ||||
|             tokenHandler = handler; | ||||
|         } | ||||
|  | ||||
|         public void addInput(String type, Supplier<String> handler) { | ||||
|             inputTypeSuppliers.put(type, handler); | ||||
|         } | ||||
|  | ||||
|         protected void addInputTemplate(String type, Function<ExecConfig.TokenOverride, String> template) { | ||||
|             inputTypeTemplates.put(type, template); | ||||
|         } | ||||
|  | ||||
|         public void addJsonInputTemplate(Function<ExecConfig.TokenOverride, Object> template) { | ||||
|             inputTypeTemplates.put(JsonType, token -> GsonUtil.get().toJson(template.apply(token))); | ||||
|         } | ||||
|  | ||||
|         public void withExitHandler(Consumer<ProcessResult> handler) { | ||||
|             onExitHandler = handler; | ||||
|         } | ||||
|  | ||||
|         public void withSuccessHandler(Consumer<ProcessResult> handler) { | ||||
|             successHandler = handler; | ||||
|         } | ||||
|  | ||||
|         public void addSuccessMapper(String type, Function<String, V> mapper) { | ||||
|             successMappers.put(type, mapper); | ||||
|         } | ||||
|  | ||||
|         public void withSuccessDefault(Function<String, V> mapper) { | ||||
|             successDefault = mapper; | ||||
|         } | ||||
|  | ||||
|         public void withFailureHandler(Consumer<ProcessResult> handler) { | ||||
|             failureHandler = handler; | ||||
|         } | ||||
|  | ||||
|         public void addFailureMapper(String type, Function<String, V> mapper) { | ||||
|             failureMappers.put(type, mapper); | ||||
|         } | ||||
|  | ||||
|         public void withFailureDefault(Function<String, V> mapper) { | ||||
|             failureDefault = mapper; | ||||
|         } | ||||
|  | ||||
|         public void addUnknownMapper(String type, Function<String, V> mapper) { | ||||
|             unknownMappers.put(type, mapper); | ||||
|         } | ||||
|  | ||||
|         public void withUnknownDefault(Function<String, V> mapper) { | ||||
|             unknownDefault = mapper; | ||||
|         } | ||||
|  | ||||
|         public V execute() { | ||||
|             log.info("Executing {}", cfg.getCommand()); | ||||
|  | ||||
|             try { | ||||
|                 ProcessExecutor psExec = executorSupplier.get(); | ||||
|  | ||||
|                 List<String> args = new ArrayList<>(); | ||||
|                 args.add(tokenHandler.apply(cfg.getCommand())); | ||||
|                 args.addAll(cfg.getArgs().stream().map(arg -> tokenHandler.apply(arg)).collect(Collectors.toList())); | ||||
|                 psExec.command(args); | ||||
|  | ||||
|                 psExec.environment(new HashMap<>(cfg.getEnv()).entrySet().stream() | ||||
|                         .peek(e -> e.setValue(tokenHandler.apply(e.getValue()))) | ||||
|                         .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); | ||||
|  | ||||
|                 inputSupplier.get().ifPresent(input -> psExec.redirectInput(IOUtils.toInputStream(input, StandardCharsets.UTF_8))); | ||||
|  | ||||
|                 ProcessResult psResult = psExec.execute(); | ||||
|                 String output = psResult.outputUTF8(); | ||||
|                 onExitHandler.accept(psResult); | ||||
|  | ||||
|                 if (cfg.getExit().getSuccess().contains(psResult.getExitValue())) { | ||||
|                     successHandler.accept(psResult); | ||||
|  | ||||
|                     return cfg.getOutput().getType() | ||||
|                             .map(type -> successMappers.getOrDefault(type, successDefault).apply(output)) | ||||
|                             .orElseGet(() -> successDefault.apply(output)); | ||||
|                 } else if (cfg.getExit().getFailure().contains(psResult.getExitValue())) { | ||||
|                     failureHandler.accept(psResult); | ||||
|  | ||||
|                     return cfg.getOutput().getType() | ||||
|                             .map(type -> failureMappers.getOrDefault(type, failureDefault).apply(output)) | ||||
|                             .orElseGet(() -> failureDefault.apply(output)); | ||||
|                 } else { | ||||
|                     unknownHandler.accept(psResult); | ||||
|  | ||||
|                     return cfg.getOutput().getType() | ||||
|                             .map(type -> unknownMappers.getOrDefault(type, unknownDefault).apply(output)) | ||||
|                             .orElseGet(() -> unknownDefault.apply(output)); | ||||
|                 } | ||||
|             } catch (RuntimeException | IOException | InterruptedException | TimeoutException e) { | ||||
|                 log.error("Failed to execute {}", cfg.getCommand()); | ||||
|                 log.debug("Internal exception:", e); | ||||
|                 throw new InternalServerError(e); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -49,7 +49,7 @@ public class GoogleFirebaseProvider extends GoogleFirebaseBackend implements ITh | ||||
|     } | ||||
|  | ||||
|     private String getMxid(UserRecord record) { | ||||
|         return new MatrixID(record.getUid(), domain).getId(); | ||||
|         return MatrixID.asAcceptable(record.getUid(), domain).getId(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -30,6 +30,7 @@ import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||
| import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||
| import io.kamax.mxisd.config.MatrixConfig; | ||||
| import io.kamax.mxisd.config.ldap.LdapConfig; | ||||
| import io.kamax.mxisd.exception.InternalServerError; | ||||
| import io.kamax.mxisd.util.GsonUtil; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||
| @@ -87,7 +88,6 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid | ||||
|     public BackendAuthResult authenticate(_MatrixID mxid, String password) { | ||||
|         log.info("Performing auth for {}", mxid); | ||||
|  | ||||
|  | ||||
|         try (LdapConnection conn = getConn()) { | ||||
|             bind(conn); | ||||
|  | ||||
| @@ -108,11 +108,13 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid | ||||
|             String[] attArray = new String[attributes.size()]; | ||||
|             attributes.toArray(attArray); | ||||
|  | ||||
|             log.debug("Base DN: {}", getBaseDn()); | ||||
|             log.debug("Query: {}", userFilter); | ||||
|             log.debug("Attributes: {}", GsonUtil.build().toJson(attArray)); | ||||
|  | ||||
|             try (EntryCursor cursor = conn.search(getBaseDn(), userFilter, SearchScope.SUBTREE, attArray)) { | ||||
|             for (String baseDN : getBaseDNs()) { | ||||
|                 log.debug("Base DN: {}", baseDN); | ||||
|  | ||||
|                 try (EntryCursor cursor = conn.search(baseDN, userFilter, SearchScope.SUBTREE, attArray)) { | ||||
|                     while (cursor.next()) { | ||||
|                         Entry entry = cursor.get(); | ||||
|                         String dn = entry.getDn().getName(); | ||||
| @@ -159,11 +161,12 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid | ||||
|                 } catch (CursorLdapReferralException e) { | ||||
|                     log.warn("Entity for {} is only available via referral, skipping", mxid); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             log.info("No match were found for {}", mxid); | ||||
|             return BackendAuthResult.failure(); | ||||
|         } catch (LdapException | IOException | CursorException e) { | ||||
|             throw new RuntimeException(e); | ||||
|             throw new InternalServerError(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -59,8 +59,8 @@ public abstract class LdapBackend { | ||||
|         return cfg; | ||||
|     } | ||||
|  | ||||
|     protected String getBaseDn() { | ||||
|         return cfg.getConnection().getBaseDn(); | ||||
|     protected List<String> getBaseDNs() { | ||||
|         return cfg.getConnection().getBaseDNs(); | ||||
|     } | ||||
|  | ||||
|     protected LdapConfig.Attribute getAt() { | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -65,18 +65,19 @@ public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProv | ||||
|             bind(conn); | ||||
|  | ||||
|             LdapConfig.Attribute atCfg = getCfg().getAttribute(); | ||||
|  | ||||
|             attributes = new ArrayList<>(attributes); | ||||
|             attributes.add(getUidAtt()); | ||||
|             String[] attArray = new String[attributes.size()]; | ||||
|             attributes.toArray(attArray); | ||||
|             String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray); | ||||
|  | ||||
|             log.debug("Base DN: {}", getBaseDn()); | ||||
|             log.debug("Query: {}", searchQuery); | ||||
|             log.debug("Attributes: {}", GsonUtil.build().toJson(attArray)); | ||||
|  | ||||
|             try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) { | ||||
|             for (String baseDN : getBaseDNs()) { | ||||
|                 log.debug("Base DN: {}", baseDN); | ||||
|  | ||||
|                 try (EntryCursor cursor = conn.search(baseDN, searchQuery, SearchScope.SUBTREE, attArray)) { | ||||
|                     while (cursor.next()) { | ||||
|                         Entry entry = cursor.get(); | ||||
|                         log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||
| @@ -93,6 +94,8 @@ public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProv | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } catch (CursorLdapReferralException e) { | ||||
|             log.warn("An entry is only available via referral, skipping"); | ||||
|         } catch (IOException | LdapException | CursorException e) { | ||||
|   | ||||
| @@ -69,11 +69,11 @@ public class LdapProfileProvider extends LdapBackend implements ProfileProvider | ||||
|             bind(conn); | ||||
|  | ||||
|             String searchQuery = buildOrQueryWithFilter(getCfg().getProfile().getFilter(), uid, getUidAtt()); | ||||
|  | ||||
|             log.debug("Base DN: {}", getBaseDn()); | ||||
|             log.debug("Query: {}", searchQuery); | ||||
|  | ||||
|             try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getAt().getName())) { | ||||
|             for (String baseDN : getBaseDNs()) { | ||||
|                 log.debug("Base DN: {}", baseDN); | ||||
|                 try (EntryCursor cursor = conn.search(baseDN, searchQuery, SearchScope.SUBTREE, getAt().getName())) { | ||||
|                     while (cursor.next()) { | ||||
|                         Entry entry = cursor.get(); | ||||
|                         log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||
| @@ -92,9 +92,10 @@ public class LdapProfileProvider extends LdapBackend implements ProfileProvider | ||||
|                             return v; | ||||
|                         } | ||||
|                     } | ||||
|             } | ||||
|                 } catch (CursorLdapReferralException e) { | ||||
|                     log.warn("An entry is only available via referral, skipping"); | ||||
|                 } | ||||
|             } | ||||
|         } catch (IOException | LdapException | CursorException e) { | ||||
|             throw new InternalServerError(e); | ||||
|         } | ||||
| @@ -111,7 +112,6 @@ public class LdapProfileProvider extends LdapBackend implements ProfileProvider | ||||
|         try (LdapConnection conn = getConn()) { | ||||
|             bind(conn); | ||||
|  | ||||
|             log.debug("Base DN: {}", getBaseDn()); | ||||
|             getCfg().getAttribute().getThreepid().forEach((medium, attributes) -> { | ||||
|                 String[] attArray = new String[attributes.size()]; | ||||
|                 attributes.toArray(attArray); | ||||
| @@ -120,7 +120,9 @@ public class LdapProfileProvider extends LdapBackend implements ProfileProvider | ||||
|  | ||||
|                 log.debug("Query for 3PID {}: {}", medium, searchQuery); | ||||
|  | ||||
|                 try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) { | ||||
|                 for (String baseDN : getBaseDNs()) { | ||||
|                     log.debug("Base DN: {}", baseDN); | ||||
|                     try (EntryCursor cursor = conn.search(baseDN, searchQuery, SearchScope.SUBTREE, attArray)) { | ||||
|                         while (cursor.next()) { | ||||
|                             Entry entry = cursor.get(); | ||||
|                             log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||
| @@ -137,11 +139,11 @@ public class LdapProfileProvider extends LdapBackend implements ProfileProvider | ||||
|                         } | ||||
|                     } catch (CursorLdapReferralException e) { | ||||
|                         log.warn("An entry is only available via referral, skipping"); | ||||
|                 } catch (IOException | LdapException | CursorException e) { | ||||
|                     } catch (LdapException | IOException | CursorException e) { | ||||
|                         throw new InternalServerError(e); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         } catch (IOException | LdapException e) { | ||||
|             throw new InternalServerError(e); | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -78,12 +78,13 @@ public class LdapThreePidProvider extends LdapBackend implements IThreePidProvid | ||||
|         // we merge 3PID specific query with global/specific filter, if one exists. | ||||
|         String tPidQuery = tPidQueryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value); | ||||
|         String searchQuery = buildWithFilter(tPidQuery, getCfg().getIdentity().getFilter()); | ||||
|  | ||||
|         log.debug("Base DN: {}", getBaseDn()); | ||||
|         log.debug("Query: {}", searchQuery); | ||||
|         log.debug("Attributes: {}", GsonUtil.build().toJson(getUidAtt())); | ||||
|  | ||||
|         try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) { | ||||
|         for (String baseDN : getBaseDNs()) { | ||||
|             log.debug("Base DN: {}", baseDN); | ||||
|  | ||||
|             try (EntryCursor cursor = conn.search(baseDN, searchQuery, SearchScope.SUBTREE, getUidAtt())) { | ||||
|                 while (cursor.next()) { | ||||
|                     Entry entry = cursor.get(); | ||||
|                     log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||
| @@ -101,6 +102,7 @@ public class LdapThreePidProvider extends LdapBackend implements IThreePidProvid | ||||
|             } catch (IOException | LdapException | CursorException e) { | ||||
|                 throw new InternalServerError(e); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|   | ||||
| @@ -148,7 +148,7 @@ public class MemoryIdentityStore implements AuthenticatorProvider, IDirectoryPro | ||||
|         for (MemoryIdentityConfig id : cfg.getIdentities()) { | ||||
|             for (MemoryThreePid threepid : id.getThreepids()) { | ||||
|                 if (req.equals(new ThreePid(threepid.getMedium(), threepid.getAddress()))) { | ||||
|                     return Optional.of(new SingleLookupReply(request, new MatrixID(id.getUsername(), mxCfg.getDomain()))); | ||||
|                     return Optional.of(new SingleLookupReply(request, MatrixID.asAcceptable(id.getUsername(), mxCfg.getDomain()))); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -20,6 +20,7 @@ | ||||
|  | ||||
| package io.kamax.mxisd.backend.rest; | ||||
|  | ||||
| import io.kamax.matrix.ThreePidMedium; | ||||
| import io.kamax.mxisd.UserID; | ||||
|  | ||||
| public class LookupSingleResponseJson { | ||||
| @@ -32,12 +33,28 @@ public class LookupSingleResponseJson { | ||||
|         return medium; | ||||
|     } | ||||
|  | ||||
|     public void setMedium(String medium) { | ||||
|         this.medium = medium; | ||||
|     } | ||||
|  | ||||
|     public void setMedium(ThreePidMedium medium) { | ||||
|         setMedium(medium.getId()); | ||||
|     } | ||||
|  | ||||
|     public String getAddress() { | ||||
|         return address; | ||||
|     } | ||||
|  | ||||
|     public void setAddress(String address) { | ||||
|         this.address = address; | ||||
|     } | ||||
|  | ||||
|     public UserID getId() { | ||||
|         return id; | ||||
|     } | ||||
|  | ||||
|     public void setId(UserID id) { | ||||
|         this.id = id; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -62,7 +62,7 @@ public class RestDirectoryProvider extends RestProvider implements IDirectoryPro | ||||
|  | ||||
|             UserDirectorySearchResult response = parser.parse(httpResponse, UserDirectorySearchResult.class); | ||||
|             for (UserDirectorySearchResult.Result result : response.getResults()) { | ||||
|                 result.setUserId(new MatrixID(result.getUserId(), mxCfg.getDomain()).getId()); | ||||
|                 result.setUserId(MatrixID.asAcceptable(result.getUserId(), mxCfg.getDomain()).getId()); | ||||
|             } | ||||
|  | ||||
|             return response; | ||||
|   | ||||
| @@ -0,0 +1,147 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.backend.rest; | ||||
|  | ||||
| import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonSyntaxException; | ||||
| import io.kamax.matrix._MatrixID; | ||||
| import io.kamax.matrix._ThreePid; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.matrix.json.InvalidJsonException; | ||||
| import io.kamax.mxisd.config.rest.RestBackendConfig; | ||||
| import io.kamax.mxisd.exception.InternalServerError; | ||||
| import io.kamax.mxisd.profile.JsonProfileRequest; | ||||
| import io.kamax.mxisd.profile.JsonProfileResult; | ||||
| import io.kamax.mxisd.profile.ProfileProvider; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.http.client.methods.CloseableHttpResponse; | ||||
| import org.apache.http.client.methods.HttpPost; | ||||
| import org.apache.http.client.utils.URIBuilder; | ||||
| import org.apache.http.entity.ContentType; | ||||
| import org.apache.http.entity.StringEntity; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.net.URISyntaxException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.*; | ||||
| import java.util.function.Function; | ||||
|  | ||||
| @Component | ||||
| public class RestProfileProvider extends RestProvider implements ProfileProvider { | ||||
|  | ||||
|     private final Logger log = LoggerFactory.getLogger(RestProfileProvider.class); | ||||
|  | ||||
|     public RestProfileProvider(RestBackendConfig cfg) { | ||||
|         super(cfg); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isEnabled() { | ||||
|         return cfg.isEnabled() && cfg.getEndpoints().getProfile().isPresent(); | ||||
|     } | ||||
|  | ||||
|     private <T> Optional<T> doRequest( | ||||
|             _MatrixID userId, | ||||
|             Function<RestBackendConfig.ProfileEndpoints, Optional<String>> endpoint, | ||||
|             Function<JsonProfileResult, Optional<T>> value | ||||
|     ) { | ||||
|         return cfg.getEndpoints().getProfile() | ||||
|                 // We get the endpoint | ||||
|                 .flatMap(endpoint) | ||||
|                 // We only continue if there is a value | ||||
|                 .filter(StringUtils::isNotBlank) | ||||
|                 // We use the endpoint | ||||
|                 .flatMap(url -> { | ||||
|                     try { | ||||
|                         URIBuilder builder = new URIBuilder(url); | ||||
|                         HttpPost req = new HttpPost(builder.build()); | ||||
|                         req.setEntity(new StringEntity(GsonUtil.get().toJson(new JsonProfileRequest(userId)), ContentType.APPLICATION_JSON)); | ||||
|                         try (CloseableHttpResponse res = client.execute(req)) { | ||||
|                             int sc = res.getStatusLine().getStatusCode(); | ||||
|                             if (sc == 404) { | ||||
|                                 log.info("Got 404 - No result found"); | ||||
|                                 return Optional.empty(); | ||||
|                             } | ||||
|  | ||||
|                             if (sc != 200) { | ||||
|                                 throw new InternalServerError("Unexpected backed status code: " + sc); | ||||
|                             } | ||||
|  | ||||
|                             String body = IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8); | ||||
|                             if (StringUtils.isBlank(body)) { | ||||
|                                 log.warn("Backend response body is empty/blank, expected JSON object with profile key"); | ||||
|                                 return Optional.empty(); | ||||
|                             } | ||||
|  | ||||
|                             Optional<JsonObject> pJson = GsonUtil.findObj(GsonUtil.parseObj(body), "profile"); | ||||
|                             if (!pJson.isPresent()) { | ||||
|                                 log.warn("Backend response body is invalid, expected JSON object with profile key"); | ||||
|                                 return Optional.empty(); | ||||
|                             } | ||||
|  | ||||
|                             JsonProfileResult profile = gson.fromJson(pJson.get(), JsonProfileResult.class); | ||||
|                             return value.apply(profile); | ||||
|                         } | ||||
|                     } catch (JsonSyntaxException | InvalidJsonException e) { | ||||
|                         log.error("Unable to parse backend response as JSON", e); | ||||
|                         throw new InternalServerError(e); | ||||
|                     } catch (URISyntaxException e) { | ||||
|                         log.error("Unable to build a valid request URL", e); | ||||
|                         throw new InternalServerError(e); | ||||
|                     } catch (IOException e) { | ||||
|                         log.error("I/O Error during backend request", e); | ||||
|                         throw new InternalServerError(); | ||||
|                     } | ||||
|                 }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<String> getDisplayName(_MatrixID userId) { | ||||
|         return doRequest(userId, p -> Optional.ofNullable(p.getDisplayName()), profile -> Optional.ofNullable(profile.getDisplayName())); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<_ThreePid> getThreepids(_MatrixID userId) { | ||||
|         return doRequest(userId, p -> Optional.ofNullable(p.getThreepids()), profile -> { | ||||
|             List<_ThreePid> t = new ArrayList<>(); | ||||
|             if (Objects.nonNull(profile.getThreepids())) { | ||||
|                 t.addAll(profile.getThreepids()); | ||||
|             } | ||||
|             return Optional.of(t); | ||||
|         }).orElseGet(Collections::emptyList); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> getRoles(_MatrixID userId) { | ||||
|         return doRequest(userId, p -> Optional.ofNullable(p.getRoles()), profile -> { | ||||
|             List<String> t = new ArrayList<>(); | ||||
|             if (Objects.nonNull(profile.getRoles())) { | ||||
|                 t.addAll(profile.getRoles()); | ||||
|             } | ||||
|             return Optional.of(t); | ||||
|         }).orElseGet(Collections::emptyList); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -60,9 +60,9 @@ public class RestThreePidProvider extends RestProvider implements IThreePidProvi | ||||
|     // TODO refactor in lookup manager with above FIXME | ||||
|     private _MatrixID getMxId(UserID id) { | ||||
|         if (UserIdType.Localpart.is(id.getType())) { | ||||
|             return new MatrixID(id.getValue(), mxCfg.getDomain()); | ||||
|             return MatrixID.asAcceptable(id.getValue(), mxCfg.getDomain()); | ||||
|         } else { | ||||
|             return new MatrixID(id.getValue()); | ||||
|             return MatrixID.asAcceptable(id.getValue()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -85,11 +85,11 @@ public abstract class SqlThreePidProvider implements IThreePidProvider { | ||||
|                         log.info("Found match: {}", uid); | ||||
|                         if (StringUtils.equals("uid", cfg.getIdentity().getType())) { | ||||
|                             log.info("Resolving as localpart"); | ||||
|                             return Optional.of(new SingleLookupReply(request, new MatrixID(uid, mxCfg.getDomain()))); | ||||
|                             return Optional.of(new SingleLookupReply(request, MatrixID.asAcceptable(uid, mxCfg.getDomain()))); | ||||
|                         } | ||||
|                         if (StringUtils.equals("mxid", cfg.getIdentity().getType())) { | ||||
|                             log.info("Resolving as MXID"); | ||||
|                             return Optional.of(new SingleLookupReply(request, new MatrixID(uid))); | ||||
|                             return Optional.of(new SingleLookupReply(request, MatrixID.asAcceptable(uid))); | ||||
|                         } | ||||
|  | ||||
|                         log.info("Identity type is unknown, skipping"); | ||||
|   | ||||
| @@ -86,7 +86,7 @@ public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider | ||||
|                     while (rSet.next()) { | ||||
|                         processRow(rSet).ifPresent(e -> { | ||||
|                             if (StringUtils.equalsIgnoreCase("localpart", query.getType())) { | ||||
|                                 e.setUserId(new MatrixID(e.getUserId(), mxCfg.getDomain()).getId()); | ||||
|                                 e.setUserId(MatrixID.asAcceptable(e.getUserId(), mxCfg.getDomain()).getId()); | ||||
|                             } | ||||
|                             result.addResult(e); | ||||
|                         }); | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/main/java/io/kamax/mxisd/config/AsyncConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/main/java/io/kamax/mxisd/config/AsyncConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.config; | ||||
|  | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; | ||||
|  | ||||
| @Configuration | ||||
| public class AsyncConfig extends WebMvcConfigurerAdapter { | ||||
|  | ||||
|     @Override | ||||
|     public void configureAsyncSupport(AsyncSupportConfigurer configurer) { | ||||
|         configurer.setDefaultTimeout(60 * 60 * 1000); // 1h in milliseconds | ||||
|         super.configureAsyncSupport(configurer); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										110
									
								
								src/main/java/io/kamax/mxisd/config/AuthenticationConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/main/java/io/kamax/mxisd/config/AuthenticationConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.config; | ||||
|  | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| @Configuration | ||||
| @ConfigurationProperties(prefix = "auth") | ||||
| public class AuthenticationConfig { | ||||
|  | ||||
|     public static class Rule { | ||||
|  | ||||
|         private String regex; | ||||
|         private transient Pattern pattern; | ||||
|         private String medium; | ||||
|  | ||||
|         public String getRegex() { | ||||
|             return regex; | ||||
|         } | ||||
|  | ||||
|         public void setRegex(String regex) { | ||||
|             this.regex = regex; | ||||
|         } | ||||
|  | ||||
|         public Pattern getPattern() { | ||||
|             return pattern; | ||||
|         } | ||||
|  | ||||
|         public void setPattern(Pattern pattern) { | ||||
|             this.pattern = pattern; | ||||
|         } | ||||
|  | ||||
|         public String getMedium() { | ||||
|             return medium; | ||||
|         } | ||||
|  | ||||
|         public void setMedium(String medium) { | ||||
|             this.medium = medium; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class User { | ||||
|  | ||||
|         private List<Rule> rules = new ArrayList<>(); | ||||
|  | ||||
|         public List<Rule> getRules() { | ||||
|             return rules; | ||||
|         } | ||||
|  | ||||
|         public void setRules(List<Rule> mappings) { | ||||
|             this.rules = mappings; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class Rewrite { | ||||
|  | ||||
|         private User user = new User(); | ||||
|  | ||||
|         public User getUser() { | ||||
|             return user; | ||||
|         } | ||||
|  | ||||
|         public void setUser(User user) { | ||||
|             this.user = user; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private Rewrite rewrite = new Rewrite(); | ||||
|  | ||||
|     public Rewrite getRewrite() { | ||||
|         return rewrite; | ||||
|     } | ||||
|  | ||||
|     public void setRewrite(Rewrite rewrite) { | ||||
|         this.rewrite = rewrite; | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public void build() { | ||||
|         getRewrite().getUser().getRules().forEach(mapping -> mapping.setPattern(Pattern.compile(mapping.getRegex()))); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -23,6 +23,7 @@ package io.kamax.mxisd.config; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.util.Objects; | ||||
|  | ||||
| @Configuration | ||||
| @@ -39,6 +40,7 @@ public class BulkLookupConfig { | ||||
|         this.enabled = enabled; | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public void build() { | ||||
|         if (Objects.isNull(enabled)) { | ||||
|             enabled = true; | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
							
								
								
									
										540
									
								
								src/main/java/io/kamax/mxisd/config/ExecConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								src/main/java/io/kamax/mxisd/config/ExecConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,540 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package io.kamax.mxisd.config; | ||||
|  | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.util.*; | ||||
|  | ||||
| @Configuration | ||||
| @ConfigurationProperties("exec") | ||||
| public class ExecConfig { | ||||
|  | ||||
|     public class IO { | ||||
|  | ||||
|         private String type; | ||||
|         private String template; | ||||
|  | ||||
|         public Optional<String> getType() { | ||||
|             return Optional.ofNullable(type); | ||||
|         } | ||||
|  | ||||
|         public void setType(String type) { | ||||
|             this.type = type; | ||||
|         } | ||||
|  | ||||
|         public Optional<String> getTemplate() { | ||||
|             return Optional.ofNullable(template); | ||||
|         } | ||||
|  | ||||
|         public void setTemplate(String template) { | ||||
|             this.template = template; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Exit { | ||||
|  | ||||
|         private List<Integer> success = Collections.singletonList(0); | ||||
|         private List<Integer> failure = Collections.singletonList(1); | ||||
|  | ||||
|         public List<Integer> getSuccess() { | ||||
|             return success; | ||||
|         } | ||||
|  | ||||
|         public void setSuccess(List<Integer> success) { | ||||
|             this.success = success; | ||||
|         } | ||||
|  | ||||
|         public List<Integer> getFailure() { | ||||
|             return failure; | ||||
|         } | ||||
|  | ||||
|         public void setFailure(List<Integer> failure) { | ||||
|             this.failure = failure; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class TokenOverride { | ||||
|  | ||||
|         private String localpart; | ||||
|         private String domain; | ||||
|         private String mxid; | ||||
|         private String password; | ||||
|         private String medium; | ||||
|         private String address; | ||||
|         private String type; | ||||
|         private String query; | ||||
|  | ||||
|         public String getLocalpart() { | ||||
|             return StringUtils.defaultIfEmpty(localpart, getToken().getLocalpart()); | ||||
|         } | ||||
|  | ||||
|         public void setLocalpart(String localpart) { | ||||
|             this.localpart = localpart; | ||||
|         } | ||||
|  | ||||
|         public String getDomain() { | ||||
|             return StringUtils.defaultIfEmpty(domain, getToken().getDomain()); | ||||
|         } | ||||
|  | ||||
|         public void setDomain(String domain) { | ||||
|             this.domain = domain; | ||||
|         } | ||||
|  | ||||
|         public String getMxid() { | ||||
|             return StringUtils.defaultIfEmpty(mxid, getToken().getMxid()); | ||||
|         } | ||||
|  | ||||
|         public void setMxid(String mxid) { | ||||
|             this.mxid = mxid; | ||||
|         } | ||||
|  | ||||
|         public String getPassword() { | ||||
|             return StringUtils.defaultIfEmpty(password, getToken().getPassword()); | ||||
|         } | ||||
|  | ||||
|         public void setPassword(String password) { | ||||
|             this.password = password; | ||||
|         } | ||||
|  | ||||
|         public String getMedium() { | ||||
|             return StringUtils.defaultIfEmpty(medium, getToken().getMedium()); | ||||
|         } | ||||
|  | ||||
|         public void setMedium(String medium) { | ||||
|             this.medium = medium; | ||||
|         } | ||||
|  | ||||
|         public String getAddress() { | ||||
|             return StringUtils.defaultIfEmpty(address, getToken().getAddress()); | ||||
|         } | ||||
|  | ||||
|         public void setAddress(String address) { | ||||
|             this.address = address; | ||||
|         } | ||||
|  | ||||
|         public String getType() { | ||||
|             return StringUtils.defaultIfEmpty(type, getToken().getType()); | ||||
|         } | ||||
|  | ||||
|         public void setType(String type) { | ||||
|             this.type = type; | ||||
|         } | ||||
|  | ||||
|         public String getQuery() { | ||||
|             return StringUtils.defaultIfEmpty(query, getToken().getQuery()); | ||||
|         } | ||||
|  | ||||
|         public void setQuery(String query) { | ||||
|             this.query = query; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Token { | ||||
|  | ||||
|         private String localpart = "{localpart}"; | ||||
|         private String domain = "{domain}"; | ||||
|         private String mxid = "{mxid}"; | ||||
|         private String password = "{password}"; | ||||
|         private String medium = "{medium}"; | ||||
|         private String address = "{address}"; | ||||
|         private String type = "{type}"; | ||||
|         private String query = "{query}"; | ||||
|  | ||||
|         public String getLocalpart() { | ||||
|             return localpart; | ||||
|         } | ||||
|  | ||||
|         public void setLocalpart(String localpart) { | ||||
|             this.localpart = localpart; | ||||
|         } | ||||
|  | ||||
|         public String getDomain() { | ||||
|             return domain; | ||||
|         } | ||||
|  | ||||
|         public void setDomain(String domain) { | ||||
|             this.domain = domain; | ||||
|         } | ||||
|  | ||||
|         public String getMxid() { | ||||
|             return mxid; | ||||
|         } | ||||
|  | ||||
|         public void setMxid(String mxid) { | ||||
|             this.mxid = mxid; | ||||
|         } | ||||
|  | ||||
|         public String getPassword() { | ||||
|             return password; | ||||
|         } | ||||
|  | ||||
|         public void setPassword(String password) { | ||||
|             this.password = password; | ||||
|         } | ||||
|  | ||||
|         public String getMedium() { | ||||
|             return medium; | ||||
|         } | ||||
|  | ||||
|         public void setMedium(String medium) { | ||||
|             this.medium = medium; | ||||
|         } | ||||
|  | ||||
|         public String getAddress() { | ||||
|             return address; | ||||
|         } | ||||
|  | ||||
|         public void setAddress(String address) { | ||||
|             this.address = address; | ||||
|         } | ||||
|  | ||||
|         public String getType() { | ||||
|             return type; | ||||
|         } | ||||
|  | ||||
|         public void setType(String type) { | ||||
|             this.type = type; | ||||
|         } | ||||
|  | ||||
|         public String getQuery() { | ||||
|             return query; | ||||
|         } | ||||
|  | ||||
|         public void setQuery(String query) { | ||||
|             this.query = query; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Process { | ||||
|  | ||||
|         private TokenOverride token = new TokenOverride(); | ||||
|         private String command; | ||||
|  | ||||
|         private List<String> args = new ArrayList<>(); | ||||
|         private Map<String, String> env = new HashMap<>(); | ||||
|         private IO input = new IO(); | ||||
|  | ||||
|         private Exit exit = new Exit(); | ||||
|         private IO output = new IO(); | ||||
|  | ||||
|         public TokenOverride getToken() { | ||||
|             return token; | ||||
|         } | ||||
|  | ||||
|         public void setToken(TokenOverride token) { | ||||
|             this.token = token; | ||||
|         } | ||||
|  | ||||
|         public String getCommand() { | ||||
|             return command; | ||||
|         } | ||||
|  | ||||
|         public void setCommand(String command) { | ||||
|             this.command = command; | ||||
|         } | ||||
|  | ||||
|         public List<String> getArgs() { | ||||
|             return args; | ||||
|         } | ||||
|  | ||||
|         public void setArgs(List<String> args) { | ||||
|             this.args = args; | ||||
|         } | ||||
|  | ||||
|         public Map<String, String> getEnv() { | ||||
|             return env; | ||||
|         } | ||||
|  | ||||
|         public void setEnv(Map<String, String> env) { | ||||
|             this.env = env; | ||||
|         } | ||||
|  | ||||
|         public void addEnv(String key, String value) { | ||||
|             this.env.put(key, value); | ||||
|         } | ||||
|  | ||||
|         public IO getInput() { | ||||
|             return input; | ||||
|         } | ||||
|  | ||||
|         public void setInput(IO input) { | ||||
|             this.input = input; | ||||
|         } | ||||
|  | ||||
|         public Exit getExit() { | ||||
|             return exit; | ||||
|         } | ||||
|  | ||||
|         public void setExit(Exit exit) { | ||||
|             this.exit = exit; | ||||
|         } | ||||
|  | ||||
|         public IO getOutput() { | ||||
|             return output; | ||||
|         } | ||||
|  | ||||
|         public void setOutput(IO output) { | ||||
|             this.output = output; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Auth extends Process { | ||||
|  | ||||
|         private Boolean enabled; | ||||
|  | ||||
|         public Boolean isEnabled() { | ||||
|             return enabled; | ||||
|         } | ||||
|  | ||||
|         public void setEnabled(Boolean enabled) { | ||||
|             this.enabled = enabled; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Directory { | ||||
|  | ||||
|         public class Search { | ||||
|  | ||||
|             private Process byName = new Process(); | ||||
|             private Process byThreepid = new Process(); | ||||
|  | ||||
|             public Process getByName() { | ||||
|                 return byName; | ||||
|             } | ||||
|  | ||||
|             public void setByName(Process byName) { | ||||
|                 this.byName = byName; | ||||
|             } | ||||
|  | ||||
|             public Process getByThreepid() { | ||||
|                 return byThreepid; | ||||
|             } | ||||
|  | ||||
|             public void setByThreepid(Process byThreepid) { | ||||
|                 this.byThreepid = byThreepid; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         private Boolean enabled; | ||||
|         private Search search = new Search(); | ||||
|  | ||||
|         public Boolean isEnabled() { | ||||
|             return enabled; | ||||
|         } | ||||
|  | ||||
|         public void setEnabled(Boolean enabled) { | ||||
|             this.enabled = enabled; | ||||
|         } | ||||
|  | ||||
|         public Search getSearch() { | ||||
|             return search; | ||||
|         } | ||||
|  | ||||
|         public void setSearch(Search search) { | ||||
|             this.search = search; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Lookup { | ||||
|  | ||||
|         private Process single = new Process(); | ||||
|         private Process bulk = new Process(); | ||||
|  | ||||
|         public Process getSingle() { | ||||
|             return single; | ||||
|         } | ||||
|  | ||||
|         public void setSingle(Process single) { | ||||
|             this.single = single; | ||||
|         } | ||||
|  | ||||
|         public Process getBulk() { | ||||
|             return bulk; | ||||
|         } | ||||
|  | ||||
|         public void setBulk(Process bulk) { | ||||
|             this.bulk = bulk; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Identity { | ||||
|  | ||||
|         private Boolean enabled; | ||||
|         private int priority; | ||||
|         private Lookup lookup = new Lookup(); | ||||
|  | ||||
|         public Boolean isEnabled() { | ||||
|             return enabled; | ||||
|         } | ||||
|  | ||||
|         public void setEnabled(Boolean enabled) { | ||||
|             this.enabled = enabled; | ||||
|         } | ||||
|  | ||||
|         public int getPriority() { | ||||
|             return priority; | ||||
|         } | ||||
|  | ||||
|         public void setPriority(int priority) { | ||||
|             this.priority = priority; | ||||
|         } | ||||
|  | ||||
|         public Lookup getLookup() { | ||||
|             return lookup; | ||||
|         } | ||||
|  | ||||
|         public void setLookup(Lookup lookup) { | ||||
|             this.lookup = lookup; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public class Profile { | ||||
|  | ||||
|         private Boolean enabled; | ||||
|         private Process displayName = new Process(); | ||||
|         private Process threePid = new Process(); | ||||
|         private Process role = new Process(); | ||||
|  | ||||
|         public Boolean isEnabled() { | ||||
|             return enabled; | ||||
|         } | ||||
|  | ||||
|         public void setEnabled(Boolean enabled) { | ||||
|             this.enabled = enabled; | ||||
|         } | ||||
|  | ||||
|         public Process getDisplayName() { | ||||
|             return displayName; | ||||
|         } | ||||
|  | ||||
|         public void setDisplayName(Process displayName) { | ||||
|             this.displayName = displayName; | ||||
|         } | ||||
|  | ||||
|         public Process getThreePid() { | ||||
|             return threePid; | ||||
|         } | ||||
|  | ||||
|         public void setThreePid(Process threePid) { | ||||
|             this.threePid = threePid; | ||||
|         } | ||||
|  | ||||
|         public Process getRole() { | ||||
|             return role; | ||||
|         } | ||||
|  | ||||
|         public void setRoles(Process role) { | ||||
|             this.role = role; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private boolean enabled; | ||||
|     private Token token = new Token(); | ||||
|     private Auth auth = new Auth(); | ||||
|     private Directory directory = new Directory(); | ||||
|     private Identity identity = new Identity(); | ||||
|     private Profile profile = new Profile(); | ||||
|  | ||||
|     public boolean isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|  | ||||
|     public void setEnabled(boolean enabled) { | ||||
|         this.enabled = enabled; | ||||
|     } | ||||
|  | ||||
|     public Token getToken() { | ||||
|         return token; | ||||
|     } | ||||
|  | ||||
|     public void setToken(Token token) { | ||||
|         this.token = token; | ||||
|     } | ||||
|  | ||||
|     public Auth getAuth() { | ||||
|         return auth; | ||||
|     } | ||||
|  | ||||
|     public void setAuth(Auth auth) { | ||||
|         this.auth = auth; | ||||
|     } | ||||
|  | ||||
|     public Directory getDirectory() { | ||||
|         return directory; | ||||
|     } | ||||
|  | ||||
|     public void setDirectory(Directory directory) { | ||||
|         this.directory = directory; | ||||
|     } | ||||
|  | ||||
|     public Identity getIdentity() { | ||||
|         return identity; | ||||
|     } | ||||
|  | ||||
|     public void setIdentity(Identity identity) { | ||||
|         this.identity = identity; | ||||
|     } | ||||
|  | ||||
|     public Profile getProfile() { | ||||
|         return profile; | ||||
|     } | ||||
|  | ||||
|     public void setProfile(Profile profile) { | ||||
|         this.profile = profile; | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public ExecConfig compute() { | ||||
|         if (Objects.isNull(getAuth().isEnabled())) { | ||||
|             getAuth().setEnabled(isEnabled()); | ||||
|         } | ||||
|  | ||||
|         if (Objects.isNull(getDirectory().isEnabled())) { | ||||
|             getDirectory().setEnabled(isEnabled()); | ||||
|         } | ||||
|  | ||||
|         if (Objects.isNull(getIdentity().isEnabled())) { | ||||
|             getIdentity().setEnabled(isEnabled()); | ||||
|         } | ||||
|  | ||||
|         if (Objects.isNull(getProfile().isEnabled())) { | ||||
|             getProfile().setEnabled(isEnabled()); | ||||
|         } | ||||
|  | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -110,6 +110,7 @@ public abstract class LdapConfig { | ||||
|         private String bindDn; | ||||
|         private String bindPassword; | ||||
|         private String baseDn; | ||||
|         private List<String> baseDNs = new ArrayList<>(); | ||||
|  | ||||
|         public boolean isTls() { | ||||
|             return tls; | ||||
| @@ -151,14 +152,24 @@ public abstract class LdapConfig { | ||||
|             this.bindPassword = bindPassword; | ||||
|         } | ||||
|  | ||||
|         @Deprecated | ||||
|         public String getBaseDn() { | ||||
|             return baseDn; | ||||
|         } | ||||
|  | ||||
|         @Deprecated | ||||
|         public void setBaseDn(String baseDn) { | ||||
|             this.baseDn = baseDn; | ||||
|         } | ||||
|  | ||||
|         public List<String> getBaseDNs() { | ||||
|             return baseDNs; | ||||
|         } | ||||
|  | ||||
|         public void setBaseDNs(List<String> baseDNs) { | ||||
|             this.baseDNs = baseDNs; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class Directory { | ||||
| @@ -253,11 +264,11 @@ public abstract class LdapConfig { | ||||
|     private boolean enabled; | ||||
|     private String filter; | ||||
|  | ||||
|     private Connection connection; | ||||
|     private Attribute attribute; | ||||
|     private Auth auth; | ||||
|     private Directory directory; | ||||
|     private Identity identity; | ||||
|     private Connection connection = new Connection(); | ||||
|     private Attribute attribute = new Attribute(); | ||||
|     private Auth auth = new Auth(); | ||||
|     private Directory directory = new Directory(); | ||||
|     private Identity identity = new Identity(); | ||||
|     private Profile profile = new Profile(); | ||||
|  | ||||
|     protected abstract String getConfigName(); | ||||
| @@ -343,8 +354,14 @@ public abstract class LdapConfig { | ||||
|             throw new IllegalStateException("LDAP port is not valid"); | ||||
|         } | ||||
|  | ||||
|         if (StringUtils.isBlank(connection.getBaseDn())) { | ||||
|             throw new ConfigurationException("ldap.connection.baseDn"); | ||||
|         // Backward compatibility with the old option | ||||
|         if (!StringUtils.isBlank(connection.baseDn)) { | ||||
|             connection.getBaseDNs().add(connection.baseDn); | ||||
|         } | ||||
|  | ||||
|         if (connection.getBaseDNs().isEmpty()) { | ||||
|             throw new ConfigurationException("ldap.connection.baseDNs", | ||||
|                     "You must specify at least one Base DN via the singular or plural config option"); | ||||
|         } | ||||
|  | ||||
|         if (StringUtils.isBlank(attribute.getUid().getType())) { | ||||
| @@ -386,7 +403,10 @@ public abstract class LdapConfig { | ||||
|         log.info("Port: {}", connection.getPort()); | ||||
|         log.info("TLS: {}", connection.isTls()); | ||||
|         log.info("Bind DN: {}", connection.getBindDn()); | ||||
|         log.info("Base DN: {}", connection.getBaseDn()); | ||||
|         log.info("Base DNs: {}"); | ||||
|         for (String baseDN : connection.getBaseDNs()) { | ||||
|             log.info("\t- {}", baseDN); | ||||
|         } | ||||
|  | ||||
|         log.info("Attribute: {}", GsonUtil.get().toJson(attribute)); | ||||
|         log.info("Auth: {}", GsonUtil.get().toJson(auth)); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Maxime Dor | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Maxime Dor | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Maxime Dor | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -30,6 +30,8 @@ import org.springframework.context.annotation.Configuration; | ||||
| import javax.annotation.PostConstruct; | ||||
| import java.net.MalformedURLException; | ||||
| import java.net.URL; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
|  | ||||
| @Configuration | ||||
| @ConfigurationProperties("rest") | ||||
| @@ -58,11 +60,44 @@ public class RestBackendConfig { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class ProfileEndpoints { | ||||
|  | ||||
|         private String displayName; | ||||
|         private String threepids; | ||||
|         private String roles; | ||||
|  | ||||
|         public String getDisplayName() { | ||||
|             return displayName; | ||||
|         } | ||||
|  | ||||
|         public void setDisplayName(String displayName) { | ||||
|             this.displayName = displayName; | ||||
|         } | ||||
|  | ||||
|         public String getThreepids() { | ||||
|             return threepids; | ||||
|         } | ||||
|  | ||||
|         public void setThreepids(String threepids) { | ||||
|             this.threepids = threepids; | ||||
|         } | ||||
|  | ||||
|         public String getRoles() { | ||||
|             return roles; | ||||
|         } | ||||
|  | ||||
|         public void setRoles(String roles) { | ||||
|             this.roles = roles; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static class Endpoints { | ||||
|  | ||||
|         private String auth; | ||||
|         private String directory; | ||||
|         private IdentityEndpoints identity = new IdentityEndpoints(); | ||||
|         private ProfileEndpoints profile; | ||||
|  | ||||
|         public String getAuth() { | ||||
|             return auth; | ||||
| @@ -88,6 +123,14 @@ public class RestBackendConfig { | ||||
|             this.identity = identity; | ||||
|         } | ||||
|  | ||||
|         public Optional<ProfileEndpoints> getProfile() { | ||||
|             return Optional.ofNullable(profile); | ||||
|         } | ||||
|  | ||||
|         public void setProfile(ProfileEndpoints profile) { | ||||
|             this.profile = profile; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private Logger log = LoggerFactory.getLogger(RestBackendConfig.class); | ||||
| @@ -121,7 +164,10 @@ public class RestBackendConfig { | ||||
|     } | ||||
|  | ||||
|     private String buildEndpointUrl(String endpoint) { | ||||
|         if (StringUtils.startsWith(endpoint, "/")) { | ||||
|         if (!StringUtils.startsWith(endpoint, "/")) { | ||||
|             return endpoint; | ||||
|         } | ||||
|  | ||||
|         if (StringUtils.isBlank(getHost())) { | ||||
|             throw new ConfigurationException("rest.host"); | ||||
|         } | ||||
| @@ -133,9 +179,6 @@ public class RestBackendConfig { | ||||
|         } | ||||
|  | ||||
|         return getHost() + endpoint; | ||||
|         } else { | ||||
|             return endpoint; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
| @@ -149,6 +192,12 @@ public class RestBackendConfig { | ||||
|             endpoints.identity.setSingle(buildEndpointUrl(endpoints.identity.getSingle())); | ||||
|             endpoints.identity.setBulk(buildEndpointUrl(endpoints.identity.getBulk())); | ||||
|  | ||||
|             if (Objects.nonNull(endpoints.profile)) { | ||||
|                 endpoints.profile.setDisplayName(buildEndpointUrl(endpoints.profile.getDisplayName())); | ||||
|                 endpoints.profile.setThreepids(buildEndpointUrl(endpoints.profile.getThreepids())); | ||||
|                 endpoints.profile.setRoles(buildEndpointUrl(endpoints.profile.getRoles())); | ||||
|             } | ||||
|  | ||||
|             log.info("Host: {}", getHost()); | ||||
|             log.info("Auth endpoint: {}", endpoints.getAuth()); | ||||
|             log.info("Directory endpoint: {}", endpoints.getDirectory()); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2018 Maxime Dor | ||||
|  * Copyright (C) 2018 Kamax Sarl | ||||
|  * | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|   | ||||
| @@ -20,13 +20,11 @@ | ||||
|  | ||||
| package io.kamax.mxisd.controller.app.v1; | ||||
|  | ||||
| import com.google.gson.JsonObject; | ||||
| import io.kamax.matrix.json.GsonUtil; | ||||
| import io.kamax.mxisd.as.AppServiceHandler; | ||||
| import io.kamax.mxisd.config.ListenerConfig; | ||||
| import io.kamax.mxisd.exception.HttpMatrixException; | ||||
| import io.kamax.mxisd.exception.NotAllowedException; | ||||
| import io.kamax.mxisd.util.GsonParser; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| @@ -36,7 +34,8 @@ import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.util.List; | ||||
| import java.io.IOException; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
|  | ||||
| import static org.springframework.web.bind.annotation.RequestMethod.GET; | ||||
| import static org.springframework.web.bind.annotation.RequestMethod.PUT; | ||||
| @@ -50,13 +49,11 @@ public class AppServiceController { | ||||
|     private final ListenerConfig cfg; | ||||
|  | ||||
|     private final String notFoundBody; | ||||
|     private final GsonParser parser; | ||||
|     private final AppServiceHandler handler; | ||||
|  | ||||
|     @Autowired | ||||
|     public AppServiceController(ListenerConfig cfg, AppServiceHandler handler) { | ||||
|         this.notFoundBody = GsonUtil.get().toJson(GsonUtil.makeObj("errcode", "io.kamax.mxisd.AS_NOT_FOUND")); | ||||
|         this.parser = new GsonParser(); | ||||
|  | ||||
|         this.cfg = cfg; | ||||
|         this.handler = handler; | ||||
| @@ -89,22 +86,19 @@ public class AppServiceController { | ||||
|     } | ||||
|  | ||||
|     @RequestMapping(value = "/transactions/{txnId:.+}", method = PUT) | ||||
|     public Object getTransaction( | ||||
|     public CompletableFuture<String> getTransaction( | ||||
|             HttpServletRequest request, | ||||
|             @RequestParam(name = "access_token", required = false) String token, | ||||
|             @PathVariable String txnId) { | ||||
|         try { | ||||
|             @PathVariable String txnId | ||||
|     ) { | ||||
|         validateToken(token); | ||||
|  | ||||
|             log.info("Processing transaction {}", txnId); | ||||
|             List<JsonObject> events = GsonUtil.asList(GsonUtil.getArray(parser.parse(request.getInputStream()), "events"), JsonObject.class); | ||||
|             handler.processTransaction(events); | ||||
|             return "{}"; | ||||
|         } catch (Throwable e) { | ||||
|             log.warn("Unable to properly process transaction", e); | ||||
|         try { | ||||
|             log.info("Received AS transaction {}", txnId); | ||||
|             return handler.processTransaction(txnId, request.getInputStream()); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException("AS Transaction " + txnId + ": I/O error when getting input", e); | ||||
|         } | ||||
|  | ||||
|         return "{}"; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -20,25 +20,18 @@ | ||||
|  | ||||
| package io.kamax.mxisd.controller.auth.v1; | ||||
|  | ||||
| import com.google.gson.*; | ||||
| import com.google.i18n.phonenumbers.NumberParseException; | ||||
| import com.google.i18n.phonenumbers.PhoneNumberUtil; | ||||
| import com.google.i18n.phonenumbers.Phonenumber; | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.JsonElement; | ||||
| import com.google.gson.JsonObject; | ||||
| import io.kamax.mxisd.auth.AuthManager; | ||||
| import io.kamax.mxisd.auth.UserAuthResult; | ||||
| import io.kamax.mxisd.controller.auth.v1.io.CredentialsValidationResponse; | ||||
| import io.kamax.mxisd.dns.ClientDnsOverwrite; | ||||
| import io.kamax.mxisd.exception.JsonMemberNotFoundException; | ||||
| import io.kamax.mxisd.exception.RemoteLoginException; | ||||
| import io.kamax.mxisd.lookup.strategy.LookupStrategy; | ||||
| import io.kamax.mxisd.util.GsonParser; | ||||
| import io.kamax.mxisd.util.GsonUtil; | ||||
| import io.kamax.mxisd.util.RestClientUtils; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.apache.http.client.methods.CloseableHttpResponse; | ||||
| import org.apache.http.client.methods.HttpGet; | ||||
| import org.apache.http.client.methods.HttpPost; | ||||
| import org.apache.http.client.utils.URIBuilder; | ||||
| import org.apache.http.impl.client.CloseableHttpClient; | ||||
| import org.apache.http.util.EntityUtils; | ||||
| import org.slf4j.Logger; | ||||
| @@ -54,10 +47,11 @@ import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.IOException; | ||||
| import java.net.URI; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| @RestController | ||||
| @CrossOrigin | ||||
| @RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE) | ||||
| @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) | ||||
| public class AuthController { | ||||
|  | ||||
|     // TODO export into SDK | ||||
| @@ -71,23 +65,9 @@ public class AuthController { | ||||
|     @Autowired | ||||
|     private AuthManager mgr; | ||||
|  | ||||
|     @Autowired | ||||
|     private LookupStrategy strategy; | ||||
|  | ||||
|     @Autowired | ||||
|     private ClientDnsOverwrite dns; | ||||
|  | ||||
|     @Autowired | ||||
|     private CloseableHttpClient client; | ||||
|  | ||||
|     private String resolveProxyUrl(HttpServletRequest req) { | ||||
|         URI target = URI.create(req.getRequestURL().toString()); | ||||
|         URIBuilder builder = dns.transform(target); | ||||
|         String urlToLogin = builder.toString(); | ||||
|         log.info("Proxy resolution: {} to {}", target.toString(), urlToLogin); | ||||
|         return urlToLogin; | ||||
|     } | ||||
|  | ||||
|     @RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST) | ||||
|     public String checkCredentials(HttpServletRequest req) { | ||||
|         try { | ||||
| @@ -120,7 +100,9 @@ public class AuthController { | ||||
|  | ||||
|     @RequestMapping(value = logV1Url, method = RequestMethod.GET) | ||||
|     public String getLogin(HttpServletRequest req, HttpServletResponse res) { | ||||
|         try (CloseableHttpResponse hsResponse = client.execute(new HttpGet(resolveProxyUrl(req)))) { | ||||
|         URI target = URI.create(req.getRequestURL().toString()); | ||||
|  | ||||
|         try (CloseableHttpResponse hsResponse = client.execute(new HttpGet(mgr.resolveProxyUrl(target)))) { | ||||
|             res.setStatus(hsResponse.getStatusLine().getStatusCode()); | ||||
|             return EntityUtils.toString(hsResponse.getEntity()); | ||||
|         } catch (IOException e) { | ||||
| @@ -130,98 +112,11 @@ public class AuthController { | ||||
|  | ||||
|     @RequestMapping(value = logV1Url, method = RequestMethod.POST) | ||||
|     public String login(HttpServletRequest req) { | ||||
|         URI target = URI.create(req.getRequestURL().toString()); | ||||
|         try { | ||||
|             JsonObject reqJsonObject = parser.parse(req.getInputStream()); | ||||
|  | ||||
|             // find 3PID in main object | ||||
|             GsonUtil.findPrimitive(reqJsonObject, "medium").ifPresent(medium -> { | ||||
|                 GsonUtil.findPrimitive(reqJsonObject, "address").ifPresent(address -> { | ||||
|                     log.info("Login request with medium '{}' and address '{}'", medium.getAsString(), address.getAsString()); | ||||
|                     strategy.findLocal(medium.getAsString(), address.getAsString()).ifPresent(lookupDataOpt -> { | ||||
|                         reqJsonObject.addProperty("user", lookupDataOpt.getMxid().getLocalPart()); | ||||
|                         reqJsonObject.remove("medium"); | ||||
|                         reqJsonObject.remove("address"); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // find 3PID in 'identifier' object | ||||
|             GsonUtil.findObj(reqJsonObject, "identifier").ifPresent(identifier -> { | ||||
|                 GsonUtil.findPrimitive(identifier, "type").ifPresent(type -> { | ||||
|  | ||||
|                     if (StringUtils.equals(type.getAsString(), "m.id.thirdparty")) { | ||||
|                         GsonUtil.findPrimitive(identifier, "medium").ifPresent(medium -> { | ||||
|                             GsonUtil.findPrimitive(identifier, "address").ifPresent(address -> { | ||||
|                                 log.info("Login request with medium '{}' and address '{}'", medium.getAsString(), address.getAsString()); | ||||
|                                 strategy.findLocal(medium.getAsString(), address.getAsString()).ifPresent(lookupDataOpt -> { | ||||
|                                     identifier.addProperty("type", "m.id.user"); | ||||
|                                     identifier.addProperty("user", lookupDataOpt.getMxid().getLocalPart()); | ||||
|                                     identifier.remove("medium"); | ||||
|                                     identifier.remove("address"); | ||||
|                                 }); | ||||
|                             }); | ||||
|                         }); | ||||
|                     } | ||||
|  | ||||
|                     if (StringUtils.equals(type.getAsString(), "m.id.phone")) { | ||||
|                         GsonUtil.findPrimitive(identifier, "number").ifPresent(number -> { | ||||
|                             GsonUtil.findPrimitive(identifier, "country").ifPresent(country -> { | ||||
|                                 log.info("Login request with phone '{}'-'{}'", country.getAsString(), number.getAsString()); | ||||
|                                 try { | ||||
|                                     PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); | ||||
|                                     Phonenumber.PhoneNumber phoneNumber = phoneUtil.parse(number.getAsString(), country.getAsString()); | ||||
|                                     String canon_phoneNumber = phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164).replace("+", ""); | ||||
|                                     String medium = "msisdn"; | ||||
|                                     strategy.findLocal(medium, canon_phoneNumber).ifPresent(lookupDataOpt -> { | ||||
|                                         identifier.addProperty("type", "m.id.user"); | ||||
|                                         identifier.addProperty("user", lookupDataOpt.getMxid().getLocalPart()); | ||||
|                                         identifier.remove("country"); | ||||
|                                         identifier.remove("number"); | ||||
|                                     }); | ||||
|                                 } catch (NumberParseException e) { | ||||
|                                     throw new RuntimeException(e); | ||||
|                                 } | ||||
|                             }); | ||||
|                         }); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // invoke 'login' on homeserver | ||||
|             HttpPost httpPost = RestClientUtils.post(resolveProxyUrl(req), gson, reqJsonObject); | ||||
|             try (CloseableHttpResponse httpResponse = client.execute(httpPost)) { | ||||
|                 // check http status | ||||
|                 int status = httpResponse.getStatusLine().getStatusCode(); | ||||
|                 log.info("http status = {}", status); | ||||
|                 if (status != 200) { | ||||
|                     // try to get possible json error message from response | ||||
|                     // otherwise just get returned plain error message | ||||
|                     String errcode = String.valueOf(httpResponse.getStatusLine().getStatusCode()); | ||||
|                     String error = EntityUtils.toString(httpResponse.getEntity()); | ||||
|                     if (httpResponse.getEntity() != null) { | ||||
|                         try { | ||||
|                             JsonObject bodyJson = new JsonParser().parse(error).getAsJsonObject(); | ||||
|                             if (bodyJson.has("errcode")) { | ||||
|                                 errcode = bodyJson.get("errcode").getAsString(); | ||||
|                             } | ||||
|                             if (bodyJson.has("error")) { | ||||
|                                 error = bodyJson.get("error").getAsString(); | ||||
|                             } | ||||
|                             throw new RemoteLoginException(status, errcode, error, bodyJson); | ||||
|                         } catch (JsonSyntaxException e) { | ||||
|                             log.warn("Response body is not JSON"); | ||||
|                         } | ||||
|                     } | ||||
|                     throw new RemoteLoginException(status, errcode, error); | ||||
|                 } | ||||
|  | ||||
|                 /// return response | ||||
|                 JsonObject respJsonObject = parser.parseOptional(httpResponse).get(); | ||||
|                 return gson.toJson(respJsonObject); | ||||
|             } catch (IOException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
|             return mgr.proxyLogin(target, IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8)); | ||||
|         } catch (IOException e) { | ||||
|             log.error("Unable to read input data from client"); | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -29,6 +29,11 @@ public class UserDirectorySearchRequest { | ||||
|         setSearchTerm(searchTerm); | ||||
|     } | ||||
|  | ||||
|     public UserDirectorySearchRequest(String type, String searchTerm) { | ||||
|         setBy(type); | ||||
|         setSearchTerm(searchTerm); | ||||
|     } | ||||
|  | ||||
|     public String getBy() { | ||||
|         return by; | ||||
|     } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -25,6 +25,10 @@ import java.util.Set; | ||||
|  | ||||
| public class UserDirectorySearchResult { | ||||
|  | ||||
|     public static UserDirectorySearchResult empty() { | ||||
|         return new UserDirectorySearchResult(); | ||||
|     } | ||||
|  | ||||
|     public static class Result { | ||||
|  | ||||
|         private String displayName; | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
| @@ -73,7 +73,7 @@ class InvitationController { | ||||
|         for (String key : request.getParameterMap().keySet()) { | ||||
|             parameters.put(key, request.getParameter(key)); | ||||
|         } | ||||
|         IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters); | ||||
|         IThreePidInvite invite = new ThreePidInvite(MatrixID.asAcceptable(sender), medium, address, roomId, parameters); | ||||
|         IThreePidInviteReply reply = mgr.storeInvite(invite); | ||||
|  | ||||
|         return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl())); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| /* | ||||
|  * mxisd - Matrix Identity Server Daemon | ||||
|  * Copyright (C) 2017 Maxime Dor | ||||
|  * Copyright (C) 2017 Kamax Sarl | ||||
|  * | ||||
|  * https://max.kamax.io/ | ||||
|  * https://www.kamax.io/ | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user