Compare commits
	
		
			228 Commits
		
	
	
		
			v0.1.0
			...
			v1.2.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 99d793b5ed | ||
|  | cb02f62b9d | ||
|  | bd9161ec9b | ||
|  | 544cab816c | ||
|  | cdb56aec1f | ||
|  | 407138e972 | ||
|  | 3eee4eaccf | ||
|  | b3aefbed77 | ||
|  | 843fa04f19 | ||
|  | f7d1a300f1 | ||
|  | f16eb264be | ||
|  | f29014be1f | ||
|  | 0c0feab0c0 | ||
|  | dd313881db | ||
|  | feb37112b2 | ||
|  | 1ab8a27fda | ||
|  | deafc420a5 | ||
|  | fce15f0e29 | ||
|  | 5b5893f407 | ||
|  | f55d5fbc80 | ||
|  | b613415dc4 | ||
|  | 0549d23d21 | ||
|  | b493ccd479 | ||
|  | 03e72ba155 | ||
|  | 32a3444a9e | ||
|  | 78a25c21ba | ||
|  | ef80f4aa30 | ||
|  | 1e413af019 | ||
|  | a0f8af820e | ||
|  | 5ef145212a | ||
|  | 91ccb75fa1 | ||
|  | ac6f549618 | ||
|  | 7f9c7aa76d | ||
|  | 02688942fd | ||
|  | 48668bcd92 | ||
|  | a9627121fa | ||
|  | 3fc86465f8 | ||
|  | d93b546e3c | ||
|  | ea15f24d41 | ||
|  | 290a32d640 | ||
|  | 10f9126cb6 | ||
|  | c3385b38dc | ||
|  | 61fec4aec7 | ||
|  | 1db76139a9 | ||
|  | a27858082c | ||
|  | ea08a80504 | ||
|  | cb3130d365 | ||
|  | 7189a4b100 | ||
|  | f71cdbf83e | ||
|  | 665a284f4b | ||
|  | 5e142eb41d | ||
|  | 9fede41904 | ||
|  | 5871bb6609 | ||
|  | 5dbaca643a | ||
|  | bf9576f9c3 | ||
|  | 773f38d349 | ||
|  | 6a5a4b3c1c | ||
|  | 7fff2448a1 | ||
|  | 6571ff76b1 | ||
|  | 16690a0329 | ||
|  | 6ac593f0fa | ||
|  | 1581ab9e07 | ||
|  | a1adca72e8 | ||
|  | e2b3920840 | ||
|  | aaa742f6d2 | ||
|  | 959feb686c | ||
|  | d9c5c5056a | ||
|  | 83fafdcfeb | ||
|  | e916ecd08b | ||
|  | 1461d8ef6c | ||
|  | 19c1214e4a | ||
|  | b976f69c39 | ||
|  | 3675da4a0f | ||
|  | 077955d538 | ||
|  | 9af0cd3615 | ||
|  | 2bf68538c3 | ||
|  | 6c02e478d9 | ||
|  | 284da779f9 | ||
|  | af161296b3 | ||
|  | 6317acd7fc | ||
|  | 30260af1f2 | ||
|  | 3b697e86ac | ||
|  | b4f0645257 | ||
|  | 0e48edf86e | ||
|  | 7e92bfa474 | ||
|  | 851e0c9d94 | ||
|  | ac1cbc4265 | ||
|  | 62711ee12e | ||
|  | 3954be2f08 | ||
|  | 640512eb27 | ||
|  | 40705b5d47 | ||
|  | 642d560ba9 | ||
|  | b6e86f5b2e | ||
|  | 4a99ec5531 | ||
|  | 9079bb25cc | ||
|  | 88e86cd0d5 | ||
|  | 8662b3f39f | ||
|  | d0aac5ac52 | ||
|  | c702a34aab | ||
|  | 786e4a8f91 | ||
|  | 8d0b0edad2 | ||
|  | 88a37c52c0 | ||
|  | 52e4a65c3c | ||
|  | 69ecef0155 | ||
|  | f7984bd36e | ||
|  | f735b3b730 | ||
|  | b6008a41f2 | ||
|  | ed2d13decf | ||
|  | 4f3ecc19f3 | ||
|  | c816217b22 | ||
|  | 182f3c4bc3 | ||
|  | 2e7b5d2a87 | ||
|  | 09208d55d7 | ||
|  | 05c76a657e | ||
|  | f3bbc7c7c6 | ||
|  | 61addd297a | ||
|  | 1de0951733 | ||
|  | d348ebd813 | ||
|  | 0499c10a2c | ||
|  | 13e248c71e | ||
|  | d221b2c5de | ||
|  | 3a1900cbb2 | ||
|  | 9f1867a030 | ||
|  | a061241291 | ||
|  | fefa81e935 | ||
|  | 1e77bf43c6 | ||
|  | c73bbf675e | ||
|  | 6c2e65ace5 | ||
|  | 33263d3cff | ||
|  | af19fed6e7 | ||
|  | 246dc4f8d1 | ||
|  | 31efa3e33f | ||
|  | bee2a5129b | ||
|  | f1e78af80b | ||
|  | e0022e549e | ||
|  | b46d047411 | ||
|  | 542c549e4e | ||
|  | ebb9a6daa0 | ||
|  | f93a94ddf1 | ||
|  | 597fc95cef | ||
|  | df81dda22d | ||
|  | 5836965a1e | ||
|  | 58d80b8eb3 | ||
|  | a4b4a3f24c | ||
|  | ace6019197 | ||
|  | 88cefeabbf | ||
|  | bf2afd8739 | ||
|  | 0b087ee08c | ||
|  | c1746697b9 | ||
|  | 5179c4dbb5 | ||
|  | 64973f57cf | ||
|  | 00a00be692 | ||
|  | 9e8dade238 | ||
|  | 9babad6b33 | ||
|  | 00896ab280 | ||
|  | f03cd76f52 | ||
|  | 0453c1db30 | ||
|  | 013be139c9 | ||
|  | 317fc367f8 | ||
|  | efc54e73f2 | ||
|  | 0182ec7251 | ||
|  | 640ccb7ef8 | ||
|  | 2e694349c9 | ||
|  | 9328fa1eb4 | ||
|  | 221d823f3b | ||
|  | 8b6eadb9ab | ||
|  | 22d8380bce | ||
|  | 0cbf1a83a5 | ||
|  | 23f717579e | ||
|  | 4eb8c95c3a | ||
|  | 0fa04e4a54 | ||
|  | f97aa8cbef | ||
|  | 37b0f29184 | ||
|  | 2befdbb54f | ||
|  | d1a6c84e6b | ||
|  | e8229b867a | ||
|  | 6fb18d5827 | ||
|  | a8488a0745 | ||
|  | 89a7416367 | ||
|  | 068c6b8555 | ||
|  | 9a90d846e6 | ||
|  | 16efe93920 | ||
|  | 8fbb45037c | ||
|  | f1196d5b72 | ||
|  | 3b4736b00f | ||
|  | 5796982f2d | ||
|  | 9e6d3ab5dd | ||
|  | 571506d1d2 | ||
|  | c00adcf575 | ||
|  | 808aed2bc3 | ||
|  | ec0a9c7b80 | ||
|  | 84afb86b77 | ||
|  | 02c5523d6d | ||
|  | d7cf31fb9a | ||
|  | d7258cd3c6 | ||
|  | 222f7f104a | ||
|  | 548dace78c | ||
|  | 001de470ca | ||
|  | 09b789dfc2 | ||
|  | 55b759a31c | ||
|  | cb0ffe0575 | ||
|  | 3cde0b9c23 | ||
|  | 2ce3bab3b6 | ||
|  | ba723f8523 | ||
|  | d2d5f80b44 | ||
|  | e2a097b2d0 | ||
|  | 45a81e5979 | ||
|  | 158060a3b0 | ||
|  | 1e3b832186 | ||
|  | 8294990b1f | ||
|  | a704ba2e6c | ||
|  | a7303fef15 | ||
|  | cd6960fa80 | ||
|  | c05ca68de4 | ||
|  | a8df386d58 | ||
|  | 4b0d549dd6 | ||
|  | 85236793e1 | ||
|  | 954dcf3a5c | ||
|  | 466a3d270e | ||
|  | a046f9f327 | ||
|  | c03573b24f | ||
|  | 694e62edee | ||
|  | 61399c7705 | ||
|  | 05d1594ac2 | ||
|  | 361596e773 | ||
|  | d57aef36ea | ||
|  | 0033d0dc1d | ||
|  | 1c43ca7666 | 
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -7,4 +7,7 @@ out/ | |||||||
| .idea/ | .idea/ | ||||||
|  |  | ||||||
| # Local dev config | # Local dev config | ||||||
| application.yaml | /application.yaml | ||||||
|  |  | ||||||
|  | # Local dev storage | ||||||
|  | /mxisd.db | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,4 +1,8 @@ | |||||||
| language: groovy | language: java | ||||||
|  | before_cache: | ||||||
| jdk: |   - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock | ||||||
|   - oraclejdk8 |   - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ | ||||||
|  | cache: | ||||||
|  |   directories: | ||||||
|  |     - $HOME/.gradle/caches/ | ||||||
|  |     - $HOME/.gradle/wrapper/ | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,11 +1,17 @@ | |||||||
| FROM openjdk:8-jre-alpine | FROM openjdk:8-jre-alpine | ||||||
|  |  | ||||||
|  | RUN apk update && apk add bash && rm -rf /var/lib/apk/* /var/cache/apk/* | ||||||
|  |  | ||||||
| VOLUME /etc/mxisd | VOLUME /etc/mxisd | ||||||
| VOLUME /var/mxisd | VOLUME /var/mxisd | ||||||
| EXPOSE 8090 | EXPOSE 8090 | ||||||
|  |  | ||||||
| ADD build/libs/mxisd.jar /mxisd.jar |  | ||||||
| ADD src/docker/start.sh /start.sh |  | ||||||
|  |  | ||||||
| ENV JAVA_OPTS="" | ENV JAVA_OPTS="" | ||||||
|  | ENV CONF_FILE_PATH="/etc/mxisd/mxisd.yaml" | ||||||
|  | ENV SIGN_KEY_PATH="/var/mxisd/sign.key" | ||||||
|  | ENV SQLITE_DATABASE_PATH="/var/mxisd/mxisd.db" | ||||||
|  |  | ||||||
| CMD [ "/start.sh" ] | CMD [ "/start.sh" ] | ||||||
|  |  | ||||||
|  | ADD src/docker/start.sh /start.sh | ||||||
|  | ADD build/libs/mxisd.jar /mxisd.jar | ||||||
|   | |||||||
							
								
								
									
										245
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,168 +1,105 @@ | |||||||
| mxisd | mxisd - Federated Matrix Identity Server | ||||||
| ----- | ---------------------------------------- | ||||||
|    |    | ||||||
|  |  | ||||||
| [Overview](#overview) | [Lookup process](#lookup-process) | [Packages](#packages) | [From source](#from-source) | [Network Discovery](#network-discovery) | [Integration](#integration) | [Support](#support) | - [Overview](#overview) | ||||||
|  | - [Features](#features) | ||||||
|  | - [Use cases](#use-cases) | ||||||
|  | - [Getting Started](#getting-started) | ||||||
|  | - [Support](#support) | ||||||
|  | - [Contribute](#contribute) | ||||||
|  | - [FAQ](#faq) | ||||||
|  | - [Contact](#contact) | ||||||
|  |  | ||||||
| # Overview | # Overview | ||||||
| mxisd is an implementation of the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html)  | mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features). | ||||||
| which provides an alternative to the vector.im and matrix.org Identity servers. | As an enhanced Identity service, it implements the [Matrix Identity service API](https://kamax.io/matrix/api/identity_service/unstable.html) | ||||||
|  | and several [extra features](#features) that greatly enhance user experience within Matrix. | ||||||
|  | It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a | ||||||
|  | single coherent product. | ||||||
|    |    | ||||||
| mxisd is federated and uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing: | mxisd is specifically designed to connect to an existing on-premise Identity store (AD/Samba/LDAP, SQL Database, | ||||||
| - Local identity stores: LDAP, etc. | Web services/app, etc.) and ease the integration of a Matrix infrastructure within an existing one. | ||||||
| - Federated identity stores: another Identity Server in charge of a specific domain, if applicable |  | ||||||
| - Configured identity stores: another Identiy Server specifically configured, if part of some sort of group trust |  | ||||||
| - Root identity store: vector.im/matrix.org central Identity Server |  | ||||||
|  |  | ||||||
| mxisd only aims to support workflow that does NOT break federation or basic lookup process of the Matrix ecosystem. | The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-Party IDentifiers) for the Homeserver and its | ||||||
|  | users. 3PIDs can be anything that uniquely and globally identify a user, like: | ||||||
|  | - Email address | ||||||
|  | - Phone number | ||||||
|  | - Skype/Live ID | ||||||
|  | - Twitter handle | ||||||
|  | - Facebook ID | ||||||
|  |  | ||||||
| **NOTE:** mxisd is **NOT** an authenticator module for homeservers. Identity servers are opaque public directories for those who publish their data on it.   | If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**. | ||||||
| For more details, please read the [Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html). |  | ||||||
|  |  | ||||||
| # Lookup Process | # Features | ||||||
| Default Lookup strategy will use a priority order and a configurable recursive/local type of request. | [Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://kamax.io/matrix/api/identity_service/unstable.html#general-principles): | ||||||
|  | - Search for people by 3PID using its own Identity stores | ||||||
|  |   ([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#association-lookup)) | ||||||
|  | - Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.) | ||||||
|  |   ([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#post-matrix-identity-api-v1-store-invite)) | ||||||
|  | - Allow users to add 3PIDs to their settings/profile | ||||||
|  |   ([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations)) | ||||||
|  | - Register accounts on your Homeserver with 3PIDs | ||||||
|  |   ([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations)) | ||||||
|  |  | ||||||
| ## E-mail | As an enhanced Identity service: | ||||||
| Given the 3PID `john.doe@example.org`, the following will be performed until a mapping is found: | - [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID, | ||||||
| - LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query. |   allowing to fetch data from: | ||||||
| - DNS: lookup another Identity Server using the domain part of an e-mail and: |   - Own Identity store(s) | ||||||
|   - Look for a SRV record under `_matrix-identity._tcp.example.org` |   - Federated Identity servers, if applicable to the 3PID | ||||||
|   - Lookup using the base domain name `example.org` |   - Arbitrary Identity servers | ||||||
| - Forwarder: Proxy the request to other configurable identity servers. |   - Central Matrix Identity servers | ||||||
|  | - [Session Control](docs/threepids/session/session.md): Extensive control of where 3PIDs are transmitted so they are not | ||||||
|  |   leaked publicly by users | ||||||
|  | - [Authentication](docs/features/authentication.md): Use your Identity stores to perform authentication in [synapse](https://github.com/matrix-org/synapse) | ||||||
|  |   via the [REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth) | ||||||
|  | - [Directory search](docs/features/directory.md) which allows you to search for users within your organisation, | ||||||
|  |   even without prior contact within Matrix using arbitrary search terms | ||||||
|  | - [Auto-fill of user profile](docs/features/authentication.md#profile-auto-fill) (Display name, 3PIDs) | ||||||
|  | - [Bridge Integration](docs/features/bridge-integration.md): Automatically bridge users without a published Matrix ID | ||||||
|  |  | ||||||
| ## Phone number | # Use cases | ||||||
| Given the phone number `+123456789`, the following lookup logic will be performed: | - Use your existing Identity stores, do not duplicate your users information | ||||||
| - LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query. | - Auto-fill user profiles with relevant information | ||||||
| - Forwarder: Proxy the request to other configurable identity servers. | - As an organisation, stay in control of your data so it is not published to other servers by default where they | ||||||
|  |   currently **cannot be removed** | ||||||
|  | - Users can directly find each other using whatever attribute is relevant within your Identity store | ||||||
|  | - Federate your Identity server so you can discover others and/or others can discover you | ||||||
|  |  | ||||||
| # Packages | # Getting started | ||||||
| See [releases]((https://github.com/kamax-io/mxisd/releases)) for native installers of supported systems.   | See the [dedicated document](docs/getting-started.md) | ||||||
| If none is available, please use other packages or build from source. |  | ||||||
|  |  | ||||||
| ## Docker |  | ||||||
| ### From source |  | ||||||
| Build the image: |  | ||||||
| ``` |  | ||||||
| docker build -t your-org/mxisd:$(git describe --tags --always --dirty) . |  | ||||||
| ``` |  | ||||||
| You can run a container of the given image and test it with the following command (adapt volumes host paths): |  | ||||||
| ``` |  | ||||||
| docker run -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t your-org/mxisd:$(git describe --tags --always --dirty) |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Debian |  | ||||||
| ### Download |  | ||||||
| See the [releases section](https://github.com/kamax-io/mxisd/releases). |  | ||||||
|  |  | ||||||
| ### From source |  | ||||||
| Requirements: |  | ||||||
| - fakeroot |  | ||||||
| - dpkg-deb |  | ||||||
|  |  | ||||||
| Run: |  | ||||||
| ``` |  | ||||||
| ./gradlew buildDeb  |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| You will find the debian package in `build/dist` |  | ||||||
|  |  | ||||||
| # From Source |  | ||||||
| ## Requirements |  | ||||||
| - JDK 1.8 |  | ||||||
|  |  | ||||||
| ## Build |  | ||||||
| ``` |  | ||||||
| git clone https://github.com/kamax-io/mxisd.git |  | ||||||
| cd mxisd |  | ||||||
| ./gradlew build |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Configure |  | ||||||
| 1. Create a new local config: `cp application.example.yaml application.yaml` |  | ||||||
| 2. Set the `server.name` value to the domain value used in your Home Server configuration |  | ||||||
| 3. Set an absolute location for the signing keys using `key.path` |  | ||||||
| 4. Provide the LDAP attributes you want to use for lookup, if you want to use one |  | ||||||
| 5. Edit an entity in your LDAP database and set the configure attribute with a Matrix ID (e.g. `@john.doe:example.org`) |  | ||||||
|  |  | ||||||
| ## Test build and configuration |  | ||||||
| Start the server in foreground to validate the build: |  | ||||||
| ``` |  | ||||||
| java -jar build/libs/mxisd.jar |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Ensure the signing key is available: |  | ||||||
| ``` |  | ||||||
| curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Validate your LDAP config and binding info (replace the e-mail): |  | ||||||
| ``` |  | ||||||
| curl "http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it |  | ||||||
| as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection |  | ||||||
| to an ID server.   |  | ||||||
| See the [Integration section](https://github.com/kamax-io/mxisd#integration) for more details. |  | ||||||
|  |  | ||||||
| ## Install |  | ||||||
| After [building](#build) the software, run all the following commands as `root` or using `sudo` |  | ||||||
| 1. Prepare files and directories: |  | ||||||
| ``` |  | ||||||
| # Create a dedicated user |  | ||||||
| useradd -r mxisd |  | ||||||
|  |  | ||||||
| # Create bin directory |  | ||||||
| mkdir /opt/mxisd |  | ||||||
|  |  | ||||||
| # Create config directory and set ownership |  | ||||||
| mkdir /etc/mxisd |  | ||||||
| chown mxisd /etc/mxisd |  | ||||||
|  |  | ||||||
| # Create data directory and set ownership |  | ||||||
| mkdir /var/opt/mxisd |  | ||||||
| chown mxisd /var/opt/mxisd |  | ||||||
|  |  | ||||||
| # Copy <repo root>/build/libs/mxisd.jar to bin directory |  | ||||||
| cp ./build/libs/mxisd.jar /opt/mxisd/ |  | ||||||
| chown mxisd /opt/mxisd/mxisd.jar |  | ||||||
| chmod a+x /opt/mxisd/mxisd.jar |  | ||||||
|  |  | ||||||
| # Create symlink for easy exec |  | ||||||
| ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| 2. Copy the config file created earlier `./application.yaml` to `/etc/mxisd/mxisd.yaml` |  | ||||||
| 3. Configure `/etc/mxisd/mxisd.yaml` with production value, `key.path` being the most important - `/var/opt/mxisd/signing.key` is recommended |  | ||||||
| 4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed |  | ||||||
| 5. Manage service for auto-startup |  | ||||||
| ``` |  | ||||||
| # Enable service |  | ||||||
| systemctl enable mxisd |  | ||||||
|  |  | ||||||
| # Start service |  | ||||||
| systemctl start mxisd |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| # Network Discovery |  | ||||||
| To allow other federated Identity Server to reach yours, configure the following DNS SRV entry (adapt to your values): |  | ||||||
| ``` |  | ||||||
| _matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. |  | ||||||
| ``` |  | ||||||
| This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation is currently possible.   |  | ||||||
|  |  | ||||||
| The port must be HTTPS capable. Typically, the port `8090` of mxisd should be behind a reverse proxy which does HTTPS. |  | ||||||
| See the [integration section](#integration) for more details. |  | ||||||
|  |  | ||||||
| # Integration |  | ||||||
| - [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS) |  | ||||||
| - [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration) |  | ||||||
|  |  | ||||||
| # Support | # Support | ||||||
| ## Community | ## Community | ||||||
| If you need help, want to report a bug or just say hi, you can reach us on Matrix at [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io)   | Over Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) ([Preview](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/)) | ||||||
| For more high-level discussion about the Identity Server architecture/API, go to [#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org) |  | ||||||
|  |  | ||||||
| ## Professional | For more high-level discussion about the Identity Server architecture/API, go to  [#matrix-identity:kamax.io](https://matrix.to/#/#matrix-identity:kamax.io) | ||||||
| If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,  |  | ||||||
| please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote. | ## Commercial | ||||||
|  | If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open | ||||||
|  | source technologies/products: | ||||||
|  | - Visit our [website](https://www.kamax.io/) to get in touch with us and get a quote. | ||||||
|  | - Come in our general Matrix room: [#kamax-matrix:kamax.io](https://matrix.to/#/#kamax-matrix:kamax.io) | ||||||
|  |  | ||||||
|  | # Contribute  | ||||||
|  | You can contribute as a community member by: | ||||||
|  | - Giving us feedback about your usage of mxisd, even if it seems unimportant or if all is working well! | ||||||
|  | - Opening issues for any weird behaviour or bug. mxisd should feel natural, let us know if it does not! | ||||||
|  | - Helping us improve the documentation: tell us what is good or not good (in an issue or in Matrix), or make a PR with | ||||||
|  | changes you feel improve the doc. | ||||||
|  | - Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3. | ||||||
|  | - [Donate!](https://liberapay.com/maximusdor/) Any donation is welcome, regardless how small or big, and will directly | ||||||
|  | be used for the fixed costs and developer time of mxisd. | ||||||
|  |  | ||||||
|  | You can contribute as an organisation/corporation by: | ||||||
|  | - Get a [support contract](#commercial). This is the best way you can help us as it ensures mxisd is | ||||||
|  | maintained regularly and you get direct access to the support team. | ||||||
|  | - Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further. | ||||||
|  |  | ||||||
|  | # FAQ | ||||||
|  | See the [dedicated document](docs/faq.md) | ||||||
|  |  | ||||||
|  | # Contact | ||||||
|  | Get in touch via: | ||||||
|  | - Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) | ||||||
|  | - Email: see our website: [Kamax.io](https://www.kamax.io) | ||||||
|   | |||||||
| @@ -1,148 +1,114 @@ | |||||||
| server: | # Sample configuration file explaining the minimum required keys to be set to run mxisd | ||||||
|  | # | ||||||
|  | # For a complete list of options, see https://github.com/kamax-matrix/mxisd | ||||||
|  |  | ||||||
|   # Indicate on which port the Identity Server will listen. | ####################### | ||||||
|   # | # Matrix config items # | ||||||
|   # This is be default an unencrypted port. | ####################### | ||||||
|   # HTTPS can be configured using Tomcat configuration properties. | # Matrix domain, same as the domain configure in your Homeserver configuration. | ||||||
|   port: 8090 | # (note: in Synapse Homeserver, the Matrix domain may be defined as 'server_name' in configuration file). | ||||||
|  | # | ||||||
|   # Realm under which this Identity Server is authoritative, required. | # This is used to build the various identifiers for identity, auth and directory. | ||||||
|   # | matrix.domain: '' | ||||||
|   # This is used to avoid unnecessary connections and endless recursive lookup. |  | ||||||
|   # e.g. domain name in e-mails. |  | ||||||
|   name: 'example.org' |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################ | ||||||
| key: | # Signing keys # | ||||||
|  | ################ | ||||||
|   # Absolute path for the Identity Server signing key, required. | # Absolute path for the Identity Server signing key. | ||||||
|   # During testing, /var/tmp/mxisd.key is a possible value | # During testing, /var/tmp/mxisd.key is a possible value | ||||||
|   # | # | ||||||
|   # For production, use a stable location like: | # For production, recommended location shall be one of the following: | ||||||
|   #   - /var/opt/mxisd/sign.key | #   - /var/opt/mxisd/sign.key | ||||||
|   #   - /var/local/mxisd/sign.key | #   - /var/local/mxisd/sign.key | ||||||
|   #   - /var/lib/mxisd/sign.key | #   - /var/lib/mxisd/sign.key | ||||||
|   path: '%SIGNING_KEYS_PATH%' | # | ||||||
|  | # The signing key is auto-generated during execution time if not present. | ||||||
|  | key.path: '' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ############################ | ||||||
|  | # Persistence config items # | ||||||
|  | ############################ | ||||||
|  |  | ||||||
| # This element contains all the configuration item for lookup strategies | # Configure the storage backend, usually a DB | ||||||
| lookup: | # Possible built-in values: | ||||||
|  | #   sqlite                      SQLite backend, default | ||||||
|  | # | ||||||
|  | #storage.backend: 'sqlite' | ||||||
|  |  | ||||||
|   # Configuration items for recursion-type of lookup | # Path to the SQLite DB file | ||||||
|   # | # | ||||||
|   # Lookup access are divided into two types: | # Examples: | ||||||
|   # - Local | #  - /var/opt/mxisd/mxisd.db | ||||||
|   # - Remote | #  - /var/local/mxisd/mxisd.db | ||||||
|   # | #  - /var/lib/mxisd/mxisd.db | ||||||
|   # This is similar to DNS lookup and recursion and is therefore prone to the same vulnerabilities. | # | ||||||
|   # By default, only non-public hosts are allowed to perform recursive lookup. | storage.provider.sqlite.database: '/path/to/mxisd.db' | ||||||
|   # |  | ||||||
|   # This will also prevent very basic endless loops where host A ask host B, which in turn is configured to ask host A, |  | ||||||
|   # which would then ask host B again, etc. |  | ||||||
|   recursive: |  | ||||||
|  |  | ||||||
|     # Enable recursive lookup globally |  | ||||||
|     enabled: true |  | ||||||
|  |  | ||||||
|     # Whitelist of CIDR that will trigger a recursive lookup. |  | ||||||
|     # The default list includes all private IPv4 address and the IPv6 loopback. |  | ||||||
|     allowedCidr: |  | ||||||
|       - '127.0.0.0/8' |  | ||||||
|       - '10.0.0.0/8' |  | ||||||
|       - '172.16.0.0/12' |  | ||||||
|       - '192.168.0.0/16' |  | ||||||
|       - '::1/128' |  | ||||||
|  |  | ||||||
|     # In case no binding is found, query an application server which implements the single lookup end-point |  | ||||||
|     # to return bridge virtual user that would allow the user to be contacted directly by the said bridge. |  | ||||||
|     # |  | ||||||
|     # If a binding is returned, the application server is not expected to sign the message as it is not meant to be |  | ||||||
|     # reachable from the outside. |  | ||||||
|     # If a signature is provided, it will be discarded/replaced by this IS implementation (to be implemented). |  | ||||||
|     # |  | ||||||
|     # IMPORTANT: This bypass the regular Invite system of the Homeserver. It will be up to the Application Server |  | ||||||
|     # to handle such invite. Also, if the bridged user were to actually join Matrix later, or if a 3PID binding is found |  | ||||||
|     # room rights and history would not be transferred, as it would appear as a regular Matrix user to the Homeserver. |  | ||||||
|     # |  | ||||||
|     # This configuration is only helpful for Application Services that want to overwrite bridging for 3PID that are |  | ||||||
|     # handled by the Homeserver. Do not enable unless the Application Server specifically supports it! |  | ||||||
|     bridge: |  | ||||||
|  |  | ||||||
|       # Enable unknown 3PID bridging globally |  | ||||||
|       enabled: false |  | ||||||
|  |  | ||||||
|       # Enable unknown 3PID bridging for hosts that are allowed to perform recursive lookups. |  | ||||||
|       # Leaving this setting to true is highly recommended in a standard setup, unless this Identity Server |  | ||||||
|       # is meant to always return a virtual user MXID even for the outside world. |  | ||||||
|       recursiveOnly: true |  | ||||||
|  |  | ||||||
|       # This mechanism can handle the following scenarios: |  | ||||||
|       # |  | ||||||
|       # - Single Application Server for all 3PID types: only configure the server value, comment out the rest. |  | ||||||
|       # |  | ||||||
|       # - Specific Application Server for some 3PID types, default server for the rest: configure the server value and |  | ||||||
|       #          each specific 3PID type. |  | ||||||
|       # |  | ||||||
|       # - Only specific 3PID types: do not configure the server value or leave it empty/blank, configure each specific |  | ||||||
|       #          3PID type. |  | ||||||
|  |  | ||||||
|       # Default application server to use for all 3PID types. Remove config item or leave empty/blank to disable. |  | ||||||
|       server: '' |  | ||||||
|  |  | ||||||
|       # Configure each 3PID type with a specific application server. Remove config item or leave empty/blank to disable. |  | ||||||
|       mappings: |  | ||||||
|         email: 'http://localhost:8091' |  | ||||||
|         msisdn: '' |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #################### | ||||||
| ldap: | # Fallback servers # | ||||||
|   enabled: false | #################### | ||||||
|   tls: false | # | ||||||
|   host: 'localhost' | # Root/Central servers to be used as final fallback when performing lookups. | ||||||
|   port: 389 | # By default, for privacy reasons, matrix.org servers are not enabled anymore. | ||||||
|   bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org' | # See the following issue: https://github.com/kamax-matrix/mxisd/issues/76 | ||||||
|   bindPassword: 'password' | # | ||||||
|   baseDn: 'CN=Users,DC=example,DC=org' | # If you would like to use them and trade away your privacy for convenience, uncomment the following option: | ||||||
|  | # | ||||||
|   # How should we resolve the Matrix ID in case of a match using the attribute. | #forward.servers: ['matrix-org'] | ||||||
|   # |  | ||||||
|   # The following type are supported: |  | ||||||
|   # - uid : the attribute only contains the UID part of the Matrix ID. e.g. 'john.doe' in @john.doe:example.org |  | ||||||
|   # - mxid : the attribute contains the full Matrix ID - e.g. '@john.doe:example.org' |  | ||||||
|   type: 'uid' |  | ||||||
|  |  | ||||||
|   # The attribute containing the binding itself. This value will be used differently depending on the type. |  | ||||||
|   # |  | ||||||
|   # /!\ This should match the synapse LDAP Authenticator 'uid' configuration /!\ |  | ||||||
|   # |  | ||||||
|   # Typical values: |  | ||||||
|   # - For type 'uid': 'userPrincipalName' or 'uid' or 'saMAccountName' |  | ||||||
|   # - For type 'mxid', regardless of the directory type, we recommend using 'pager' as it is a standard attribute and |  | ||||||
|   #   is typically not used. |  | ||||||
|   attribute: 'userPrincipalName' |  | ||||||
|  |  | ||||||
|   # Configure each 3PID type with a dedicated query. |  | ||||||
|   mappings: |  | ||||||
|     email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))" |  | ||||||
|  |  | ||||||
|     # Phone numbers query. |  | ||||||
|     # |  | ||||||
|     # Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN |  | ||||||
|     # This format does not include international prefix (+ or 00) and therefore has to be put in the query. |  | ||||||
|     # Adapt this to your needs for each attribute. |  | ||||||
|     msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################ | ||||||
|  | # 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 | ||||||
|  |  | ||||||
| forward: |  | ||||||
|  |  | ||||||
|   # List of forwarders to use to try to match a 3PID. | ############### | ||||||
|   # | # SQL Backend # | ||||||
|   # Each server will be tried in the given order, going to the next if no binding was found or an error occurred. | ############### | ||||||
|   # These are the current root Identity Servers of the Matrix network. | # If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB, | ||||||
|   servers: | # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/sql.md | ||||||
|     - "https://matrix.org" |  | ||||||
|     - "https://vector.im" |  | ||||||
|  | ################ | ||||||
|  | # 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################################################# | ||||||
|  | # 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 | ||||||
|  | # | ||||||
|  | #### E-mail invite sender | ||||||
|  | # | ||||||
|  | # SMTP host | ||||||
|  | threepid.medium.email.connectors.smtp.host: "smtp.example.org" | ||||||
|  |  | ||||||
|  | # SMTP port | ||||||
|  | threepid.medium.email.connectors.smtp.port: 587 | ||||||
|  |  | ||||||
|  | # TLS mode for the connection. | ||||||
|  | # | ||||||
|  | # Possible values: | ||||||
|  | #  0    Disable TLS entirely | ||||||
|  | #  1    Enable TLS if supported by server (default) | ||||||
|  | #  2    Force TLS and fail if not available | ||||||
|  | # | ||||||
|  | #threepid.medium.email.connectors.smtp.tls: 1 | ||||||
|  |  | ||||||
|  | # Login for SMTP | ||||||
|  | threepid.medium.email.connectors.smtp.login: "matrix-identity@example.org" | ||||||
|  |  | ||||||
|  | # Password for the account | ||||||
|  | threepid.medium.email.connectors.smtp.password: "ThePassword" | ||||||
|  |  | ||||||
|  | # The e-mail to send as. | ||||||
|  | threepid.medium.email.identity.from: "matrix-identity@example.org" | ||||||
|   | |||||||
							
								
								
									
										144
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,10 +1,8 @@ | |||||||
| import java.util.regex.Pattern |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * mxisd - Matrix Identity Server Daemon |  * 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 |  * This program is free software: you can redistribute it and/or modify | ||||||
|  * it under the terms of the GNU Affero General Public License as |  * it under the terms of the GNU Affero General Public License as | ||||||
| @@ -20,7 +18,9 @@ import java.util.regex.Pattern | |||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| apply plugin: 'groovy' | import java.util.regex.Pattern | ||||||
|  |  | ||||||
|  | apply plugin: 'java' | ||||||
| apply plugin: 'org.springframework.boot' | apply plugin: 'org.springframework.boot' | ||||||
|  |  | ||||||
| def confFileName = "application.example.yaml" | def confFileName = "application.example.yaml" | ||||||
| @@ -40,11 +40,14 @@ def debBuildConfPath = "${debBuildBasePath}${debConfPath}" | |||||||
| def debBuildDataPath = "${debBuildBasePath}${debDataPath}" | def debBuildDataPath = "${debBuildBasePath}${debDataPath}" | ||||||
| def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}" | def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}" | ||||||
|  |  | ||||||
|  | def dockerImageName = "kamax/mxisd" | ||||||
|  | def dockerImageTag = "${dockerImageName}:${gitVersion()}" | ||||||
|  |  | ||||||
| String gitVersion() { | String gitVersion() { | ||||||
|     def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") |     def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") | ||||||
|     ByteArrayOutputStream out = new ByteArrayOutputStream() |     ByteArrayOutputStream out = new ByteArrayOutputStream() | ||||||
|     exec { |     exec { | ||||||
|         commandLine = [ 'git', 'describe', '--always', '--dirty' ] |         commandLine = ['git', 'describe', '--tags', '--always', '--dirty'] | ||||||
|         standardOutput = out |         standardOutput = out | ||||||
|     } |     } | ||||||
|     def v = out.toString().replace(System.lineSeparator(), '') |     def v = out.toString().replace(System.lineSeparator(), '') | ||||||
| @@ -63,23 +66,28 @@ buildscript { | |||||||
|  |  | ||||||
| repositories { | repositories { | ||||||
|     mavenCentral() |     mavenCentral() | ||||||
|  |     maven { url "https://kamax.io/maven/releases/" } | ||||||
|  |     maven { url "https://kamax.io/maven/snapshots/" } | ||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     // We are a groovy project |  | ||||||
|     compile 'org.codehaus.groovy:groovy-all:2.4.7' |  | ||||||
|  |  | ||||||
|     // Easy file management |     // Easy file management | ||||||
|     compile 'commons-io:commons-io:2.5' |     compile 'commons-io:commons-io:2.5' | ||||||
|  |  | ||||||
|     // Spring Boot - standalone app |     // Spring Boot - standalone app | ||||||
|     compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE' |     compile 'org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE' | ||||||
|  |  | ||||||
|  |     // Thymeleaf for HTML templates | ||||||
|  |     compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.RELEASE" | ||||||
|  |  | ||||||
|  |     // Matrix Java SDK | ||||||
|  |     compile 'io.kamax:matrix-java-sdk:0.0.14-8-g0e57ec6' | ||||||
|  |  | ||||||
|     // ed25519 handling |     // ed25519 handling | ||||||
|     compile 'net.i2p.crypto:eddsa:0.1.0' |     compile 'net.i2p.crypto:eddsa:0.1.0' | ||||||
|  |  | ||||||
|     // LDAP connector |     // LDAP connector | ||||||
|     compile 'org.apache.directory.api:api-all:1.0.0-RC2' |     compile 'org.apache.directory.api:api-all:1.0.0' | ||||||
|  |  | ||||||
|     // DNS lookups |     // DNS lookups | ||||||
|     compile 'dnsjava:dnsjava:2.1.8' |     compile 'dnsjava:dnsjava:2.1.8' | ||||||
| @@ -87,13 +95,42 @@ dependencies { | |||||||
|     // HTTP connections |     // HTTP connections | ||||||
|     compile 'org.apache.httpcomponents:httpclient:4.5.3' |     compile 'org.apache.httpcomponents:httpclient:4.5.3' | ||||||
|  |  | ||||||
|     // JSON |  | ||||||
|     compile 'com.google.code.gson:gson:2.8.1' |  | ||||||
|  |  | ||||||
|     // Phone numbers validation |     // Phone numbers validation | ||||||
|     compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1' |     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' | ||||||
|  |  | ||||||
|  |     // Google Firebase Authentication backend | ||||||
|  |     compile 'com.google.firebase:firebase-admin:5.3.0' | ||||||
|  |  | ||||||
|  |     // ORMLite | ||||||
|  |     compile 'com.j256.ormlite:ormlite-jdbc:5.0' | ||||||
|  |  | ||||||
|  |     // Connection Pool | ||||||
|  |     compile 'com.mchange:c3p0:0.9.5.2' | ||||||
|  |  | ||||||
|  |     // SQLite | ||||||
|  |     compile 'org.xerial:sqlite-jdbc:3.20.0' | ||||||
|  |  | ||||||
|  |     // PostgreSQL | ||||||
|  |     compile 'org.postgresql:postgresql:42.1.4' | ||||||
|  |  | ||||||
|  |     // MariaDB/MySQL | ||||||
|  |     compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2' | ||||||
|  |  | ||||||
|  |     // Twilio SDK for SMS | ||||||
|  |     compile 'com.twilio.sdk:twilio:7.14.5' | ||||||
|  |  | ||||||
|  |     // 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 'junit:junit:4.12' | ||||||
|  |     testCompile 'com.github.tomakehurst:wiremock:2.8.0' | ||||||
| } | } | ||||||
|  |  | ||||||
| springBoot { | springBoot { | ||||||
| @@ -104,6 +141,17 @@ springBoot { | |||||||
|     ] |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | processResources { | ||||||
|  |     doLast { | ||||||
|  |         copy { | ||||||
|  |             from('build/resources/main/application.yaml') { | ||||||
|  |                 rename 'application.yaml', 'mxisd.yaml' | ||||||
|  |             } | ||||||
|  |             into 'build/resources/main' | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| task buildDeb(dependsOn: build) { | task buildDeb(dependsOn: build) { | ||||||
|     doLast { |     doLast { | ||||||
|         def v = gitVersion() |         def v = gitVersion() | ||||||
| @@ -122,8 +170,8 @@ task buildDeb(dependsOn: build) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         ant.chmod( |         ant.chmod( | ||||||
|             file: "${debBuildBinPath}/mxisd.jar", |                 file: "${debBuildBinPath}/mxisd.jar", | ||||||
|             perm: 'a+x' |                 perm: 'a+x' | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         copy { |         copy { | ||||||
| @@ -133,10 +181,16 @@ task buildDeb(dependsOn: build) { | |||||||
|             into debBuildConfPath |             into debBuildConfPath | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         ant.replace( |         ant.replaceregexp( | ||||||
|             file: "${debBuildConfPath}/${debConfFileName}", |                 file: "${debBuildConfPath}/${debConfFileName}", | ||||||
|             token: '%SIGNING_KEYS_PATH%', |                 match: "key.path:(.*)", | ||||||
|             value: "${debDataPath}/signing.key" |                 replace: "key.path: '${debDataPath}/signing.key'" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         ant.replaceregexp( | ||||||
|  |                 file: "${debBuildConfPath}/${debConfFileName}", | ||||||
|  |                 match: "storage.provider.sqlite.database:(.*)", | ||||||
|  |                 replace: "storage.provider.sqlite.database: '${debDataPath}/mxisd.db'" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         copy { |         copy { | ||||||
| @@ -145,25 +199,25 @@ task buildDeb(dependsOn: build) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         ant.replace( |         ant.replace( | ||||||
|             file: "${debBuildDebianPath}/control", |                 file: "${debBuildDebianPath}/control", | ||||||
|             token: 'Version: 0', |                 token: 'Version: 0', | ||||||
|             value: "Version: ${v}" |                 value: "Version: ${v}" | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         ant.replace( |         ant.replace( | ||||||
|             file: "${debBuildDebianPath}/postinst", |                 file: "${debBuildDebianPath}/postinst", | ||||||
|             token: '%DEB_DATA_DIR%', |                 token: '%DEB_DATA_DIR%', | ||||||
|             value: debDataPath |                 value: debDataPath | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         ant.chmod( |         ant.chmod( | ||||||
|             file: "${debBuildDebianPath}/postinst", |                 file: "${debBuildDebianPath}/postinst", | ||||||
|             perm: 'a+x' |                 perm: 'a+x' | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         ant.chmod( |         ant.chmod( | ||||||
|             file: "${debBuildDebianPath}/prerm", |                 file: "${debBuildDebianPath}/prerm", | ||||||
|             perm: 'a+x' |                 perm: 'a+x' | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         copy { |         copy { | ||||||
| @@ -173,12 +227,32 @@ task buildDeb(dependsOn: build) { | |||||||
|  |  | ||||||
|         exec { |         exec { | ||||||
|             commandLine( |             commandLine( | ||||||
|                 'fakeroot', |                     'fakeroot', | ||||||
|                 'dpkg-deb', |                     'dpkg-deb', | ||||||
|                 '-b', |                     '-b', | ||||||
|                 debBuildBasePath, |                     debBuildBasePath, | ||||||
|                 "${project.buildDir}/dist" |                     "${project.buildDir}/dist" | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | task dockerBuild(type: Exec, dependsOn: build) { | ||||||
|  |     commandLine 'docker', 'build', '-t', dockerImageTag, project.rootDir | ||||||
|  |  | ||||||
|  |     doLast { | ||||||
|  |         exec { | ||||||
|  |             commandLine 'docker', 'tag', dockerImageTag, "${dockerImageName}:latest-dev" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | task dockerPush(type: Exec) { | ||||||
|  |     commandLine 'docker', 'push', dockerImageTag | ||||||
|  |  | ||||||
|  |     doLast { | ||||||
|  |         exec { | ||||||
|  |             commandLine 'docker', 'push', "${dockerImageName}:latest-dev" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								docs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docs/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | # Table of Contents | ||||||
|  | - [Identity Concepts in Matrix](concepts.md) | ||||||
|  | - [Getting Started](getting-started.md) | ||||||
|  | - [Build from sources](build.md) (Optional) | ||||||
|  | - Installation | ||||||
|  |   - [Debian package](install/debian.md) | ||||||
|  |   - [ArchLinux](install/archlinux.md) | ||||||
|  |   - [Docker](install/docker.md) | ||||||
|  |   - [From source](install/source.md) | ||||||
|  | - [Architecture overview](architecture.md) | ||||||
|  | - [Configuration](configure.md) | ||||||
|  | - Features | ||||||
|  |   - [Authentication](features/authentication.md) | ||||||
|  |   - [Directory search](features/directory.md) | ||||||
|  |   - [Identity](features/identity.md) | ||||||
|  |   - [Federation](features/federation.md) | ||||||
|  |   - [Bridge integration](features/bridge-integration.md) | ||||||
|  | - [Identity Stores](stores/README.md) | ||||||
|  | - Notifications | ||||||
|  |   - Handlers | ||||||
|  |     - [Basic](threepids/notification/basic-handler.md) | ||||||
|  |     - [SendGrid](threepids/notification/sendgrid-handler.md) | ||||||
|  | - [Sessions](threepids/session/session.md) | ||||||
|  |   - [Views](threepids/session/session-views.md) | ||||||
|  | - [FAQ](faq.md) | ||||||
							
								
								
									
										1
									
								
								docs/_config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/_config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | theme: jekyll-theme-cayman | ||||||
							
								
								
									
										40
									
								
								docs/architecture.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								docs/architecture.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | # Architecture | ||||||
|  | ## Overview | ||||||
|  | ### Basic setup with default settings | ||||||
|  | ``` | ||||||
|  |  Client | ||||||
|  |    | | ||||||
|  | TCP 443 | ||||||
|  |    |   +---------------------+            +---------------------------+ | ||||||
|  |    +-> | Reverse proxy       |            | Homeserver                | | ||||||
|  |        |                     | TCP 8008   |                           | | ||||||
|  |        |  /_matrix/* -------------------> | - 3PID invite from client | | ||||||
|  |        |                     |            |   |                       | | ||||||
|  |        |  /_matrix/identity/ |            |   |                       | | ||||||
|  |        +--|------------------+            +---|-----------------------+ | ||||||
|  |           |                                   | | ||||||
|  |           +<---------------------------------<+ | ||||||
|  |           | | ||||||
|  |           |   +-------------------+ | ||||||
|  |  TCP 8090 +-> | mxisd             | | ||||||
|  |               |                   | | ||||||
|  |               | - Profile's 3PIDs | | ||||||
|  |               | - 3PID Invites    | | ||||||
|  |               +-|-----------------+ | ||||||
|  |                 | | ||||||
|  |              TCP 443 | ||||||
|  |                 |  +------------------------+ | ||||||
|  |                 |  | Remote Federated       | | ||||||
|  |                 |  | mxisd servers          | | ||||||
|  |                 |  |                        | | ||||||
|  |                 +--> - 3PID Invites         | | ||||||
|  |                    +------------------------+ | ||||||
|  | ``` | ||||||
|  | ### With Authentication | ||||||
|  | See the [dedicated document](features/authentication.md). | ||||||
|  |  | ||||||
|  | ### With Directory | ||||||
|  | See the [dedicated document](features/directory.md). | ||||||
|  |  | ||||||
|  | ### With Federation | ||||||
|  | See the [dedicated document](features/federation.md). | ||||||
							
								
								
									
										73
									
								
								docs/build.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								docs/build.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | # From source | ||||||
|  | - [Binaries](#binaries) | ||||||
|  |   - [Requirements](#requirements) | ||||||
|  |   - [Build](#build) | ||||||
|  | - [Debian package](#debian-package) | ||||||
|  | - [Docker image](#docker-image) | ||||||
|  | - [Next steps](#next-steps) | ||||||
|  |  | ||||||
|  | ## Binaries | ||||||
|  | ### Requirements | ||||||
|  | - JDK 1.8 | ||||||
|  |  | ||||||
|  | ### Build | ||||||
|  | ```bash | ||||||
|  | git clone https://github.com/kamax-matrix/mxisd.git | ||||||
|  | cd mxisd | ||||||
|  | ./gradlew build | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Create a new configuration file by coping `application.example.yaml` to `application.yaml` and edit to your needs.   | ||||||
|  | For advanced configuration, see the [Configure section](configure.md).   | ||||||
|  | **NOTE**: `application.yaml` is also called `mxisd.yaml` in some specific installations. | ||||||
|  |  | ||||||
|  | Start the server in foreground to validate the build and configuration: | ||||||
|  | ```bash | ||||||
|  | java -jar build/libs/mxisd.jar | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Ensure the signing key is available: | ||||||
|  | ```bash | ||||||
|  | $ curl 'http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0' | ||||||
|  | {"public_key":"..."} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Test basic recursive lookup (requires Internet connection with access to TCP 443): | ||||||
|  | ```bash | ||||||
|  | $ curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=mxisd-federation-test@kamax.io' | ||||||
|  | {"address":"mxisd-federation-test@kamax.io","medium":"email","mxid":"@mxisd-lookup-test:kamax.io",...} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If you enabled LDAP, you can also validate your config with a similar request after replacing the `address` value with | ||||||
|  | something present within your LDAP | ||||||
|  | ```bash | ||||||
|  | curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it | ||||||
|  | as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection | ||||||
|  | to an ID server.   | ||||||
|  |  | ||||||
|  | Next step: [Install your compiled binaries](install/source.md) | ||||||
|  |  | ||||||
|  | ## Debian package | ||||||
|  | Requirements: | ||||||
|  | - fakeroot | ||||||
|  | - dpkg-deb | ||||||
|  |  | ||||||
|  | [Build mxisd](#build) then: | ||||||
|  | ```bash | ||||||
|  | ./gradlew buildDeb  | ||||||
|  | ``` | ||||||
|  | You will find the debian package in `build/dist`.   | ||||||
|  | Then follow the instruction in the [Debian package](install/debian.md) document. | ||||||
|  |  | ||||||
|  | ## Docker image | ||||||
|  | [Build mxisd](#build) then: | ||||||
|  | ```bash | ||||||
|  | ./gradlew dockerBuild | ||||||
|  | ``` | ||||||
|  | Then follow the instructions in the [Docker install](install/docker.md#configure) document. | ||||||
|  |  | ||||||
|  | ## Next steps | ||||||
|  | - [Integrate with your infrastructure](getting-started.md#integrate) | ||||||
							
								
								
									
										43
									
								
								docs/concepts.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docs/concepts.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | # Concepts | ||||||
|  | - [Matrix](#matrix) | ||||||
|  | - [mxisd](#mxisd) | ||||||
|  |  | ||||||
|  | ## Matrix | ||||||
|  | The following concepts are part of the Matrix ecosystem and specification. | ||||||
|  |  | ||||||
|  | ### 3PID | ||||||
|  | `3PID` stands for Third-Party Identifier.   | ||||||
|  | It is also commonly written: | ||||||
|  | - `3pid` | ||||||
|  | - `tpid` | ||||||
|  |  | ||||||
|  | A 3PID is a globally unique canonical identifier which is made of: | ||||||
|  | - Medium, which describes what network it belongs to (Email, Phone, Twitter, Discord, etc.) | ||||||
|  | - Address, the actual value people typically use on a daily basis. | ||||||
|  |  | ||||||
|  | mxisd core mission is to map those identifiers to Matrix User IDs. | ||||||
|  |  | ||||||
|  | ### Homeserver | ||||||
|  | Where a user **account and data** are stored. | ||||||
|  |  | ||||||
|  | ### Identity server | ||||||
|  | An Identity server: | ||||||
|  | - Does lookup of 3PIDs to User Matrix IDs. | ||||||
|  | - Does validate 3PIDs ownership, typically by sending a code that the user has to enter in an application/on a website. | ||||||
|  | - Does send notifications about room invites where no Matrix User ID could be found for the invitee. | ||||||
|  |  | ||||||
|  | An Identity server: | ||||||
|  | - **DOES NOT** store user accounts. | ||||||
|  | - **DOES NOT** store user data. | ||||||
|  | - **DOES NOT** allow migration of user account and/or data between homeservers.  | ||||||
|  |  | ||||||
|  | ### 3PID session | ||||||
|  | The fact to validate a 3PID (email, phone number, etc.) via the introduction of a token which was sent to the 3PID address. | ||||||
|  |  | ||||||
|  | ## mxisd | ||||||
|  | The following concepts are specific to mxisd. | ||||||
|  |  | ||||||
|  | ### Identity store | ||||||
|  | Where your user accounts and 3PID mappings are stored. | ||||||
|  |  | ||||||
|  | mxisd itself **DOES NOT STORE** user accounts or 3PID mappings. | ||||||
							
								
								
									
										102
									
								
								docs/configure.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								docs/configure.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | # Configuration | ||||||
|  | - [Concepts](#concepts) | ||||||
|  |   - [Syntax](#syntax) | ||||||
|  |   - [Variables](#variables) | ||||||
|  | - [Matrix](#matrix) | ||||||
|  | - [Server](#server) | ||||||
|  | - [Storage](#storage) | ||||||
|  | - [Identity stores](#identity-stores) | ||||||
|  | - [3PID Validation sessions](#3pid-validation-sessions) | ||||||
|  | - [Notifications](#notifications) | ||||||
|  |  | ||||||
|  | ## Concepts | ||||||
|  | ### Syntax | ||||||
|  | The configuration file is [YAML](http://yaml.org/) based, allowing two types of syntax. | ||||||
|  |  | ||||||
|  | Properties-like: | ||||||
|  | ```yaml | ||||||
|  | my.config.item: 'value' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Object-like: | ||||||
|  | ```yaml | ||||||
|  | my: | ||||||
|  |   config: | ||||||
|  |     item: 'value' | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | These can also be combined within the same file.   | ||||||
|  | Both syntax will be used interchangeably in these documents. | ||||||
|  |  | ||||||
|  | ### Variables | ||||||
|  | It is possible to copy the value of a configuration item into another using the syntax `${config.key.item}`.   | ||||||
|  | Example that will copy the value of `matrix.domain` into `server.name`: | ||||||
|  | ```yaml | ||||||
|  | matrix: | ||||||
|  |   domain: 'example.org' | ||||||
|  |  | ||||||
|  | server: | ||||||
|  |   name: '${matrix.domain}' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **WARNING:** mxisd might overwrite/adapt some values during launch. Those changes will not be reflected into copied keys. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Matrix | ||||||
|  | `matrix.domain` | ||||||
|  | Matrix domain name, same as the Homeserver, used to build appropriate Matrix IDs | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | `matrix.identity.servers` | ||||||
|  | Namespace to create arbitrary list of Identity servers, usable in other parts of the configuration | | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | matrix.identity.servers: | ||||||
|  |   myOtherServers: | ||||||
|  |     - 'https://other1.example.org' | ||||||
|  |     - 'https://other2.example.org' | ||||||
|  | ``` | ||||||
|  | Create a list under the label `root` containing a single Identity server, `https://matrix.org` | ||||||
|  |  | ||||||
|  | ## Server | ||||||
|  | - `server.name`: Public hostname of mxisd, if different from the Matrix domain. | ||||||
|  | - `server.port`: HTTP port to listen on (unencrypted) | ||||||
|  | - `server.publicUrl`: Defaults to `https://${server.name}` | ||||||
|  |  | ||||||
|  | ## Storage | ||||||
|  | ### SQLite | ||||||
|  | `storage.provider.sqlite.database`: Absolute location of the SQLite database | ||||||
|  |  | ||||||
|  | ## Identity stores | ||||||
|  | See the [Identity stores](stores/README.md) for specific configuration | ||||||
|  |  | ||||||
|  | ## 3PID Validation sessions | ||||||
|  | See the dedicated documents: | ||||||
|  | - [Flow](threepids/session/session.md) | ||||||
|  | - [Branding](threepids/session/session-views.md) | ||||||
|  |  | ||||||
|  | ## Notifications | ||||||
|  | - `notification.handler.<3PID medium>`: Handler to use for the given 3PID medium. Repeatable. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | notification.handler.email: 'sendgrid' | ||||||
|  | notification.handler.msisdn: 'raw' | ||||||
|  | ``` | ||||||
|  | - Emails notifications would use the `sendgrid` handler, which define its own configuration under `notification.handlers.sendgrid` | ||||||
|  | - Phone notification would use the `raw` handler, basic default built-in handler of mxisd | ||||||
|  |  | ||||||
|  | ### Handlers | ||||||
|  | - `notification.handers.<handler ID>`: Handler-specific configuration for the given handler ID. Repeatable. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | notification.handlers.raw: ... | ||||||
|  | notification.handlers.sendgrid: ... | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Built-in: | ||||||
|  | - [Raw](threepids/notification/basic-handler.md) | ||||||
|  | - [SendGrid](threepids/notification/sendgrid-handler.md) | ||||||
							
								
								
									
										87
									
								
								docs/faq.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								docs/faq.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | # Frequently Asked Questions | ||||||
|  | ### This is all very complicated and I'm getting confused with all the words, concepts and diagrams - Help! | ||||||
|  | Matrix is still a very young protocol and there are a whole lot of rough edges.   | ||||||
|  | Identity in Matrix is one of the most difficult topic, mainly as it has not received much love in the past years. | ||||||
|  |  | ||||||
|  | We have tried our best to put together documentation that requires almost no knowledge of Matrix inner workings to get a | ||||||
|  | first basic setup running which relies on you reading the documentation in the right order: | ||||||
|  | - [The Concepts](concepts.md) in few words. | ||||||
|  | - [Getting Started](getting-started.md) step-by-step to a minimal working install. | ||||||
|  | - [Identity stores](stores/README.md) you wish to fetch data from. | ||||||
|  | - [Features](features) you are interested in that will use your Identity store(s) data. | ||||||
|  |  | ||||||
|  | **IMPORTANT**: Be aware that mxisd tries to fit within the current protocol and existing products and basic understanding | ||||||
|  | of the Matrix protocol is required for some advanced features. | ||||||
|  |  | ||||||
|  | If all fails, come over to [the project room](https://matrix.to/#/#mxisd:kamax.io) and we'll do our best to get you | ||||||
|  | started and answer questions you might have. | ||||||
|  |  | ||||||
|  | ### Do I need to use mxisd if I run a Homeserver? | ||||||
|  | No, but it is strongly recommended, even if you don't use any Identity store or integration. | ||||||
|  |  | ||||||
|  | In its default configuration, mxisd uses other federated public servers when performing queries.   | ||||||
|  | It can also [be configured](features/identity.md#lookups) to use the central matrix.org servers, giving you access to at | ||||||
|  | least the same information as if you were not running it. | ||||||
|  |  | ||||||
|  | It will also give your users a choice to make their 3PIDs available publicly, ensuring they are made aware of the | ||||||
|  | privacy consequences, which is not the case with the central Matrix.org servers. | ||||||
|  |  | ||||||
|  | So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice | ||||||
|  | simple features on top of it. | ||||||
|  |  | ||||||
|  | ### I'm not sure I understand what an "Identity server" is supposed to be or do... | ||||||
|  | The current Identity service API is more a placeholder, as the Matrix devs did not have time so far to really work on | ||||||
|  | what they want to do with that part of the ecosystem. Therefore, "Identity" is currently a misleading word and concept. | ||||||
|  | Given the scope of the current Identity Service API, it would be best called "Invitation service". | ||||||
|  |  | ||||||
|  | Because the current scope is so limited and no integration is done with the Homeserver, there was a big lack of features | ||||||
|  | for groups/corporations/organisation. This is where mxisd comes in. | ||||||
|  |  | ||||||
|  | mxisd implements the Identity Service API and also a set of features which are expected by regular users, truly living | ||||||
|  | up to its "Identity server" name. | ||||||
|  |  | ||||||
|  | ### Can I migrate my existing account on another Matrix server with mxisd? | ||||||
|  | No. | ||||||
|  |  | ||||||
|  | Accounts cannot currently migrate/move from one server to another.   | ||||||
|  | See a [brief explanation document](concepts.md) about Matrix and mxisd concepts and vocabulary. | ||||||
|  |  | ||||||
|  | ### I already use the synapse LDAP3 auth provider. Why should I care about mxisd? | ||||||
|  | The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained and | ||||||
|  | only handles on specific flow: validate credentials at login. | ||||||
|  |  | ||||||
|  | It does not: | ||||||
|  | - Auto-provision user profiles | ||||||
|  | - Integrate with Identity management | ||||||
|  | - Integrate with Directory searches | ||||||
|  |  | ||||||
|  | mxisd is a replacement and enhancement of it, offering coherent results in all areas, which the LDAP3 auth provider | ||||||
|  | does not. | ||||||
|  |  | ||||||
|  | ### Sydent is the official Identity server implementation of the Matrix team. Why not use that? | ||||||
|  | You can, but [sydent](https://github.com/matrix-org/sydent): | ||||||
|  | - [should not be used and/or self-hosted](https://github.com/matrix-org/sydent/issues/22) | ||||||
|  | - is not meant to be linked to a specific Homeserver / domain | ||||||
|  | - cannot handle federation or proxy lookups, effectively isolating your users from the rest of the network | ||||||
|  | - forces you to duplicate all your identity data, so people can be found by 3PIDs | ||||||
|  | - forces users to enter all their emails and phone numbers manually in their profile | ||||||
|  |  | ||||||
|  | So really, you should go with mxisd. | ||||||
|  |  | ||||||
|  | ### Will I loose access to the central Matrix.org/Vector.im Identity data if I use mxisd? | ||||||
|  | No. | ||||||
|  |  | ||||||
|  | In its default configuration, mxisd does not talk to the central Identity server matrix.org to avoid leaking your private | ||||||
|  | data and those of people you might know. | ||||||
|  |  | ||||||
|  | mxisd [can be configured](features/identity.md#lookups) to talk to the central Identity servers if you wish. | ||||||
|  |  | ||||||
|  | ### So mxisd is just a big hack! I don't want to use non-official features! | ||||||
|  | mxisd primary concerns are your privacy and to always be compatible with the Matrix ecosystem and the Identity service API.   | ||||||
|  | Whenever the API will be updated and/or enhanced, mxisd will follow, remaining 100% compatible with the ecosystem. | ||||||
|  |  | ||||||
|  | ### Should I use mxisd if I don't host my own Homeserver? | ||||||
|  | No. | ||||||
|  |  | ||||||
|  | It is possible, but it is not supported and the scope of features will be extremely limited. | ||||||
|  | Please consider hosting your own Homeserver and using mxisd alongside it. | ||||||
							
								
								
									
										155
									
								
								docs/features/authentication.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								docs/features/authentication.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | |||||||
|  | # Authentication | ||||||
|  | - [Description](#description) | ||||||
|  | - [Basic](#basic) | ||||||
|  |   - [Overview](#overview) | ||||||
|  |   - [synapse](#synapse) | ||||||
|  |   - [mxisd](#mxisd) | ||||||
|  |   - [Validate](#validate) | ||||||
|  |   - [Next steps](#next-steps) | ||||||
|  |     - [Profile auto-fil](#profile-auto-fill) | ||||||
|  | - [Advanced](#advanced) | ||||||
|  |   - [Overview](#overview-1) | ||||||
|  |   - [Requirements](#requirements) | ||||||
|  |   - [Configuration](#configuration) | ||||||
|  |     - [Reverse Proxy](#reverse-proxy) | ||||||
|  |       - [Apache2](#apache2) | ||||||
|  |     - [DNS Overwrite](#dns-overwrite) | ||||||
|  |  | ||||||
|  | ## Description | ||||||
|  | Authentication is an enhanced feature of mxisd to ensure coherent and centralized identity management.   | ||||||
|  | It allows to use Identity stores configured in mxisd to authenticate users on your Homeserver. | ||||||
|  |  | ||||||
|  | Authentication is divided into two parts: | ||||||
|  | - [Basic](#basic): authenticate with a regular username. | ||||||
|  | - [Advanced](#advanced): same as basic with extra ability to authenticate using a 3PID. | ||||||
|  |  | ||||||
|  | ## Basic | ||||||
|  | Authentication by username is possible by linking synapse and mxisd together using a specific module for synapse, also | ||||||
|  | known as password provider. | ||||||
|  |  | ||||||
|  | ### Overview | ||||||
|  | An overview of the Basic Authentication process: | ||||||
|  | ``` | ||||||
|  |                                                                                     Identity stores | ||||||
|  |  Client                                                                             +------+ | ||||||
|  |    |                                            +-------------------------+    +--> | LDAP | | ||||||
|  |    |   +---------------+  /_matrix/identity     | mxisd                   |    |    +------+ | ||||||
|  |    +-> | Reverse proxy | >------------------+   |                         |    | | ||||||
|  |        +--|------------+                    |   |                         |    |    +--------+ | ||||||
|  |           |                                 +-----> Check ID stores     >------+--> | SQL DB | | ||||||
|  |      Login request                          |   |                         |    |    +--------+ | ||||||
|  |           |                                 |   |     |                   |    | | ||||||
|  |           |   +--------------------------+  |   +-----|-------------------+    +-->  ... | ||||||
|  |           +-> | Homeserver               |  |         | | ||||||
|  |               |                          |  |         | | ||||||
|  |               | - Validate credentials >----+         | | ||||||
|  |               |   Using REST auth module |            | | ||||||
|  |               |                          |            | | ||||||
|  |               | - Auto-provision <-------------------<+ | ||||||
|  |               |   user profiles          |    If valid credentials and supported by Identity store(s) | ||||||
|  |               +--------------------------+ | ||||||
|  | ``` | ||||||
|  | Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md) | ||||||
|  |  | ||||||
|  | ### Synapse | ||||||
|  | - Install the [password provider](https://github.com/kamax-io/matrix-synapse-rest-auth) | ||||||
|  | - Edit your **synapse** configuration: | ||||||
|  |   - As described by the auth module documentation | ||||||
|  |   - Set `endpoint` to `http://mxisdAddress:8090` - Replace `mxisdAddress` by an IP/host name that provides a direct | ||||||
|  |   connection to mxisd.   | ||||||
|  |   This **MUST NOT** be a public address, and SHOULD NOT go through a reverse proxy. | ||||||
|  | - Restart synapse | ||||||
|  |  | ||||||
|  | ### mxisd | ||||||
|  | - Configure and enable at least one [Identity store](../stores/README.md) | ||||||
|  | - Restart mxisd | ||||||
|  |  | ||||||
|  | ### Validate | ||||||
|  | Login on the Homeserver using credentials present in one of your Identity stores. | ||||||
|  |  | ||||||
|  | ## Next steps | ||||||
|  | ### Profile auto-fill | ||||||
|  | Auto-filling user profile depends on its support by your configured Identity stores.   | ||||||
|  | See your Identity store [documentation](../stores/README.md) on how to enable the feature. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Advanced | ||||||
|  | The Authentication feature allows users to login to their Homeserver by using their 3PIDs in a configured Identity store. | ||||||
|  |  | ||||||
|  | ### Overview | ||||||
|  | This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/login` as depicted below: | ||||||
|  | ``` | ||||||
|  |             +----------------------------+ | ||||||
|  |             |  Reverse Proxy             | | ||||||
|  |             |                            | | ||||||
|  |             |                            |     Step 1    +---------------------------+     Step 2 | ||||||
|  |             |                            |               |                           | | ||||||
|  | Client+---->| /_matrix/client/r0/login +---------------->|                           | Look up address  +---------+ | ||||||
|  |             |                      ^     |               |  mxisd - Identity server  +----------------->| Backend | | ||||||
|  |             |                      |     |               |                           |                  +---------+ | ||||||
|  |             | /_matrix/* +--+      +---------------------+                           | | ||||||
|  |             |               |            |               +---------------+-----------+ | ||||||
|  |             |               |            |     Step 4                    | | ||||||
|  |             |               |            |                               | Step 3 | ||||||
|  |             +---------------|------------+                               | | ||||||
|  |                             |                                            | /_matrix/client/r0/login | ||||||
|  |                             |                       +--------------+     | | ||||||
|  |                             |                       |              |     | | ||||||
|  |                             +---------------------->|  Homeserver  |<----+ | ||||||
|  |                                                     |              | | ||||||
|  |                                                     +--------------+ | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Steps of user authentication using a 3PID: | ||||||
|  | 1. The intercepted login request is directly sent to mxisd instead of the Homeserver. | ||||||
|  | 2. Identity stores are queried for a matching user identity in order to modify the request to use the user name. | ||||||
|  | 3. The Homeserver, from which the request was intercepted, is queried using the request at previous step. | ||||||
|  |    Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port. | ||||||
|  | 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) | ||||||
|  |  | ||||||
|  | ### Configuration | ||||||
|  | #### Reverse Proxy | ||||||
|  | ##### Apache2 | ||||||
|  | The specific configuration to put under the relevant `VirtualHost`: | ||||||
|  | ```apache | ||||||
|  | ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login | ||||||
|  | ``` | ||||||
|  | `ProxyPreserveHost` or equivalent **must** be enabled to detect to which Homeserver mxisd should talk to when building results. | ||||||
|  |  | ||||||
|  | Your VirtualHost should now look similar to: | ||||||
|  | ```apache | ||||||
|  | <VirtualHost *:443> | ||||||
|  |     ServerName example.org | ||||||
|  |      | ||||||
|  |     ... | ||||||
|  |      | ||||||
|  |     ProxyPreserveHost on | ||||||
|  |     ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login | ||||||
|  |     ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity | ||||||
|  |     ProxyPass /_matrix http://localhost:8008/_matrix | ||||||
|  | </VirtualHost> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### DNS Overwrite | ||||||
|  | Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with | ||||||
|  | the internal IP of the Homeserver so it can talk to it directly to integrate its directory search. | ||||||
|  |  | ||||||
|  | To do so, put the following configuration in your mxisd configuration: | ||||||
|  | ```yaml | ||||||
|  | dns.overwrite.homeserver.client: | ||||||
|  |   - name: 'example.org' | ||||||
|  |     value: 'http://localhost:8008' | ||||||
|  | ``` | ||||||
|  | `name` must be the hostname of the URL that clients use when connecting to the Homeserver. | ||||||
|  | You can use `${server.name}` to auto-populate the `value` using the `server.name` configuration option and avoid duplicating it. | ||||||
|  | In case the hostname is the same as your Matrix domain and `server.name` is not explicitely set in the config, `server.name` will default to | ||||||
|  | `matrix.domain` and will still probably have the correct value. | ||||||
|  |  | ||||||
|  | `value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`. | ||||||
							
								
								
									
										28
									
								
								docs/features/bridge-integration.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								docs/features/bridge-integration.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | # Bridge Integration | ||||||
|  | To help natural bridge integration into the regular usage of a Matrix client, mxisd provides a way for bridge to reply | ||||||
|  | to 3PID queries if no mapping was found, allowing seamless bridging to a network. | ||||||
|  |  | ||||||
|  | This is performed by implementing a specific endpoint on the bridge to map a 3PID lookup to a virtual user. | ||||||
|  |  | ||||||
|  | **NOTE**: This document is incomplete and might be misleading. In doubt, come in our Matrix room.   | ||||||
|  | You can also look at our [Email Bridge README](https://github.com/kamax-io/matrix-appservice-email#mxisd) for an example | ||||||
|  | of working configuration. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ```yaml | ||||||
|  | lookup.recursive.bridge.enabled: <boolean> | ||||||
|  | lookup.recursive.bridge.recursiveOnly: <boolean> | ||||||
|  | lookup.recursive.bridge.server: <URL to the bridge endpoint for all 3PID medium> | ||||||
|  | lookup.recursive.bridge.mappings: | ||||||
|  |   <3PID MEDIUM HERE>: <URL to dedicated bridge for that medium> | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Integration | ||||||
|  | Implement a simplified version of the [Identity service single lookup endpoint](https://kamax.io/matrix/api/identity_service/unstable.html#get-matrix-identity-api-v1-lookup) | ||||||
|  | with only the following parameters needed: | ||||||
|  | - `address` | ||||||
|  | - `medium` | ||||||
|  | - `mxid` | ||||||
|  |  | ||||||
|  | Or an empty object if no resolution exists or desired. | ||||||
							
								
								
									
										149
									
								
								docs/features/directory.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								docs/features/directory.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | # User Directory | ||||||
|  | - [Description](#description) | ||||||
|  | - [Overview](#overview) | ||||||
|  | - [Requirements](#requirements) | ||||||
|  | - [Configuration](#configuration) | ||||||
|  |   - [Reverse Proxy](#reverse-proxy) | ||||||
|  |     - [Apache2](#apache2) | ||||||
|  |     - [nginx](#nginx) | ||||||
|  |   - [DNS Overwrite](#dns-overwrite) | ||||||
|  | - [Next steps](#next-steps) | ||||||
|  |  | ||||||
|  | ## Description | ||||||
|  | This feature allows you to search for existing and/or potential users that are already present in your Identity backend | ||||||
|  | or that already share a room with you on the Homeserver. | ||||||
|  |  | ||||||
|  | Without any integration, synapse: | ||||||
|  | - Only search within the users **already** known to you or in public rooms | ||||||
|  | - Only search on the Display Name and the Matrix ID | ||||||
|  |  | ||||||
|  | By enabling this feature, you can by default: | ||||||
|  | - Search on Matrix ID, Display name and 3PIDs (Email, phone numbers) of any users already in your configured backend | ||||||
|  | - Search for users which you are not in contact with yet. Super useful for corporations who want to give Matrix access | ||||||
|  | internally, so users can just find themselves **prior** to having any common room(s) | ||||||
|  | - Add extra attributes of your backend to extend the search | ||||||
|  | - Include your homeserver search results to those found by mxisd | ||||||
|  |  | ||||||
|  | By integrating mxisd, you get the default behaviour and a bunch of extras, ensuring your users will always find each other. | ||||||
|  |  | ||||||
|  | ## Overview | ||||||
|  | This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/user_directory/search` like so: | ||||||
|  | ``` | ||||||
|  |            +----------------------------------------------+ | ||||||
|  | Client --> | Reverse proxy                                                                         Step 2 | ||||||
|  |            |                                              Step 1    +-------------------------+ | ||||||
|  |            |   /_matrix/client/r0/user_directory/search ----------> |                         |  Search in   +---------+ | ||||||
|  |            |                        /\                              | mxisd - Identity server | -----------> | Backend | | ||||||
|  |            |   /_matrix/*            \----------------------------- |                         |  all users   +---------+ | ||||||
|  |            |        |            Step 4: Send back merged results   +-------------------------+ | ||||||
|  |            +        |                                                            | | ||||||
|  |                     |                                                          Step 3 | ||||||
|  |                     |                                                            | | ||||||
|  |                     |    +------------+                                Search in known users | ||||||
|  |                     \--> | Homeserver | <----------------------------------------/ | ||||||
|  |                          +------------+   /_matrix/client/r0/user_directory/search | ||||||
|  | ``` | ||||||
|  | Steps: | ||||||
|  | 1. The intercepted request is directly sent to mxisd instead of the Homeserver. | ||||||
|  | 2. Identity stores are queried for any match on the search value sent by the client. | ||||||
|  | 3. The Homeserver, from which the request was intercepted, is queried using the same request as the client. | ||||||
|  |    Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port. | ||||||
|  | 4. Results from Identity stores and the Homeserver are merged together and sent back to the client, believing it was the HS | ||||||
|  | which directly answered the request. | ||||||
|  |  | ||||||
|  | ## Requirements | ||||||
|  | - Reverse proxy setup, which you should already have in place if you use mxisd | ||||||
|  | - At least one compatible [Identity store](../stores/README.md) enabled | ||||||
|  |    | ||||||
|  | ## Configuration | ||||||
|  | ### Reverse Proxy | ||||||
|  | #### Apache2 | ||||||
|  | The specific configuration to put under the relevant `VirtualHost`: | ||||||
|  | ```apache | ||||||
|  | ProxyPass /_matrix/client/r0/user_directory/ http://0.0.0.0:8090/_matrix/client/r0/user_directory/ | ||||||
|  | ``` | ||||||
|  | `ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building | ||||||
|  | results. | ||||||
|  |  | ||||||
|  | Your `VirtualHost` should now look like this: | ||||||
|  | ```apache | ||||||
|  | <VirtualHost *:443> | ||||||
|  |     ServerName example.org | ||||||
|  |      | ||||||
|  |     ... | ||||||
|  |      | ||||||
|  |     ProxyPreserveHost on | ||||||
|  |     ProxyPass /_matrix/client/r0/user_directory/ http://localhost:8090/_matrix/client/r0/user_directory/ | ||||||
|  |     ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity | ||||||
|  |     ProxyPass /_matrix http://localhost:8008/_matrix | ||||||
|  | </VirtualHost> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### nginx | ||||||
|  | The specific configuration to add under your `server` section is: | ||||||
|  | ```nginx | ||||||
|  | location /_matrix/client/r0/user_directory { | ||||||
|  |     proxy_pass http://0.0.0.0:8090/_matrix/client/r0/user_directory; | ||||||
|  |     proxy_set_header Host $host; | ||||||
|  |     proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Your `server` section should now look like this: | ||||||
|  | ```nginx | ||||||
|  | server { | ||||||
|  |     listen 443 ssl; | ||||||
|  |     server_name example.org; | ||||||
|  |      | ||||||
|  |     ... | ||||||
|  |      | ||||||
|  |     location /_matrix/client/r0/user_directory { | ||||||
|  |         proxy_pass http://localhost:8090/_matrix/client/r0/user_directory; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     location /_matrix/identity { | ||||||
|  |         proxy_pass http://localhost:8090/_matrix/identity; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     location /_matrix { | ||||||
|  |         proxy_pass http://localhost:8008/_matrix; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### DNS Overwrite | ||||||
|  | Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with | ||||||
|  | the internal IP of the Homeserver so it can talk to it directly to integrate its directory search. | ||||||
|  |  | ||||||
|  | To do so, use the following configuration: | ||||||
|  | ```yaml | ||||||
|  | dns.overwrite.homeserver.client: | ||||||
|  |   - name: 'example.org' | ||||||
|  |     value: 'http://localhost:8008' | ||||||
|  | ``` | ||||||
|  | `name` must be the hostname of the URL that clients use when connecting to the Homeserver.   | ||||||
|  | In case the hostname is the same as your Matrix domain, you can use `${matrix.domain}` to auto-populate the value using | ||||||
|  | the `matrix.domain` configuration option and avoid duplicating it. | ||||||
|  |  | ||||||
|  | `value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`. | ||||||
|  |  | ||||||
|  | ## Next steps | ||||||
|  | ### Homeserver results | ||||||
|  | You can configure if the Homeserver should be queried at all when doing a directory search.   | ||||||
|  | To disable Homeserver results, set the following in mxisd configuration file: | ||||||
|  | ```yaml | ||||||
|  | directory.exclude.homeserver: true | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 3PID exclusion in search | ||||||
|  | You can configure if the 3PID should also be included when doing a directory search. | ||||||
|  | By default, a search is performed on the 3PIDs. If you would like to not include them: | ||||||
|  | ```yaml | ||||||
|  | directory.exclude.threepid: true | ||||||
|  | ``` | ||||||
							
								
								
									
										72
									
								
								docs/features/experimental/application-service.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								docs/features/experimental/application-service.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | # Integration as an Application Service | ||||||
|  | **WARNING:** These features are currently highly experimental. They can be removed or modified without notice.   | ||||||
|  | All the features requires a Homeserver capable of connecting Application Services. | ||||||
|  |  | ||||||
|  | ## Email notification for Room invites by Matrix ID | ||||||
|  | This feature allows for users found in Identity stores to be instantly notified about Room Invites, regardless if their | ||||||
|  | account was already provisioned on the Homeserver. | ||||||
|  |  | ||||||
|  | ### Requirements | ||||||
|  | - [Identity store(s)](../../stores/README.md) supporting the Profile feature | ||||||
|  | - At least one email entry in the identity store for each user that could be invited. | ||||||
|  |  | ||||||
|  | ### Configuration | ||||||
|  | In your mxisd config file: | ||||||
|  | ```yaml | ||||||
|  | matrix: | ||||||
|  |   listener: | ||||||
|  |     url:  '<URL TO THE CS API OF THE HOMESERVER>' | ||||||
|  |     localpart: 'appservice-mxisd' | ||||||
|  |     token: | ||||||
|  |       hs: 'HS_TOKEN_CHANGE_ME' | ||||||
|  |  | ||||||
|  | synapseSql: | ||||||
|  |   enabled: false ## Do not use this line if Synapse is used as an Identity Store | ||||||
|  |   type: '<DB TYPE>' | ||||||
|  |   connection: '<DB CONNECTION URL>' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | The `synapseSql` section is used to retrieve display names which are not directly accessible in this mode. | ||||||
|  | For details about `type` and `connection`, see the [relevant documentation](../../stores/synapse.md). | ||||||
|  | If you do not configure it, some placeholders will not be available in the notification, like the Room name. | ||||||
|  |  | ||||||
|  | You can also change the default template of the notification using the `generic.matrixId` template option.   | ||||||
|  | See [the Template generator documentation](../../threepids/notification/template-generator.md) for more info. | ||||||
|  |  | ||||||
|  | ### Homeserver integration | ||||||
|  | #### Synapse | ||||||
|  | Create a new appservice registration file. Futher config will assume it is in `/etc/matrix-synapse/appservice-mxisd.yaml` | ||||||
|  | ```yaml | ||||||
|  | id: "appservice-mxisd" | ||||||
|  | url: "http://127.0.0.1:8090" | ||||||
|  | as_token: "AS_TOKEN_CHANGE_ME" | ||||||
|  | hs_token: "HS_TOKEN_CHANGE_ME" | ||||||
|  | sender_localpart: "appservice-mxisd" | ||||||
|  | namespaces: | ||||||
|  |   users: | ||||||
|  |     - regex: "@*" | ||||||
|  |       exclusive: false | ||||||
|  |   aliases: [] | ||||||
|  |   rooms: [] | ||||||
|  | ``` | ||||||
|  | `id`: An arbitrary unique string to identify the AS.   | ||||||
|  | `url`: mxisd to reach mxisd. This ideally should be HTTP and not going through any reverse proxy.   | ||||||
|  | `as_token`: Arbitrary value used by mxisd when talking to the HS. Not currently used.   | ||||||
|  | `hs_token`: Arbitrary value used by synapse when talking to mxisd. Must match `token.hs` in mxisd config. | ||||||
|  | `sender_localpart`: Username for the mxisd itself on the HS. Default configuration should be kept.   | ||||||
|  | `namespaces`: To be kept as is.   | ||||||
|  |  | ||||||
|  | Edit your `homeserver.yaml` and add a new entry to the appservice config file, which should look something like this: | ||||||
|  | ```yaml | ||||||
|  | app_service_config_files: | ||||||
|  |   - '/etc/matrix-synapse/appservice-mxisd.yaml' | ||||||
|  |   - ... | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Restart synapse when done to register mxisd. | ||||||
|  |  | ||||||
|  | #### Others | ||||||
|  | See your Homeserver documentation on how to integrate. | ||||||
|  |  | ||||||
|  | ### Test | ||||||
|  | Invite a user which is part of your domain while an appropriate Identity store is used. | ||||||
							
								
								
									
										12
									
								
								docs/features/experimental/profile.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs/features/experimental/profile.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # Profile enhancement | ||||||
|  | **WARNING**: Alpha feature, not officially supported. Do not use. | ||||||
|  |  | ||||||
|  | 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. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ### Reverse proxy | ||||||
|  | #### Apache | ||||||
|  | ```apache | ||||||
|  | ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1" | ||||||
|  | ``` | ||||||
							
								
								
									
										49
									
								
								docs/features/federation.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/features/federation.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | # Federation | ||||||
|  | Federation is the process by which domain owners can make compatible 3PIDs mapping auto-discoverable by looking for another | ||||||
|  | Federated Identity server using the DNS domain part of the 3PID. | ||||||
|  |  | ||||||
|  | Emails are the best candidate for this kind of resolution which are DNS domain based already.   | ||||||
|  | On the other hand, Phone numbers cannot be resolved this way. | ||||||
|  |  | ||||||
|  | For 3PIDs which are not compatible with the DNS system, mxisd can be configured to talk to fallback Identity servers like | ||||||
|  | the central matrix.org one. See the [Identity feature](identity.md#lookups) for instructions on how to enable it. | ||||||
|  |  | ||||||
|  | Outbound federation is enabled by default while inbound federation is opt-in and require a specific DNS record. | ||||||
|  |  | ||||||
|  | ## Overview | ||||||
|  | ``` | ||||||
|  |               +-------------------+   +-------------> +----------+ | ||||||
|  |               | mxisd             |   |               | Backends | | ||||||
|  |               |                   |   |      +------> +----------+ | ||||||
|  |               |                   |   |      | | ||||||
|  |               | Invites / Lookups |   |      | | ||||||
|  |  Federated    | +--------+        |   |      | | ||||||
|  |  Identity  ---->| Remote |>-----------+      | | ||||||
|  |  Server       | +--------+        |          | | ||||||
|  |               |                   |          | | ||||||
|  |               | +--------+        |          |        +-------------------+ | ||||||
|  |  Homeserver --->| Local  |>------------------+------> | Remote Federated  | | ||||||
|  |  and clients  | +--------+        |                   | mxisd servers     | | ||||||
|  |               +-------------------+                   +-------------------+ | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Inbound | ||||||
|  | If you would like to be reachable for lookups over federation, create the following DNS SRV entry and replace | ||||||
|  | `matrix.example.com` by your Identity server public hostname: | ||||||
|  | ``` | ||||||
|  | _matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. | ||||||
|  | ```  | ||||||
|  |  | ||||||
|  | The port must be HTTPS capable which is what you get in a regular setup with a reverse proxy from 443 to TCP 8090 of mxisd. | ||||||
|  |  | ||||||
|  | ## Outbound | ||||||
|  | If you would like to disable outbound federation and isolate your identity server from the rest of the Matrix network, | ||||||
|  | use the following mxisd configuration options: | ||||||
|  | ```yaml | ||||||
|  | lookup.recursive.enabled: false | ||||||
|  | invite.resolution.recursive: false | ||||||
|  | session.policy.validation.forLocal.toRemote.enabled: false | ||||||
|  | session.policy.validation.forRemote.toRemote.enabled: false | ||||||
|  | ```  | ||||||
|  |  | ||||||
|  | There is currently no way to selectively disable federation towards specific servers, but this feature is planned. | ||||||
							
								
								
									
										31
									
								
								docs/features/identity.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								docs/features/identity.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | # Identity | ||||||
|  | **WARNING**: This document is incomplete and can be missleading. | ||||||
|  |  | ||||||
|  | Implementation of the [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html). | ||||||
|  |  | ||||||
|  | ## Lookups | ||||||
|  | If you would like to use the central matrix.org Identity server to ensure maximum discovery at the cost of potentially | ||||||
|  | leaking all your contacts information, add the following to your configuration: | ||||||
|  | ```yaml | ||||||
|  | forward.servers: | ||||||
|  |   - 'matrix-org' | ||||||
|  | ``` | ||||||
|  | **NOTE:** You should carefully consider enabling this option, which is discouraged.   | ||||||
|  | For more info, see the [relevant issue](https://github.com/kamax-matrix/mxisd/issues/76). | ||||||
|  |  | ||||||
|  | ## Room Invitations | ||||||
|  | Resolution can be customized using the following configuration: | ||||||
|  |  | ||||||
|  | `invite.resolution.recursive`   | ||||||
|  | - Default value: `true`   | ||||||
|  | - Description: Control if the pending invite resolution should be done recursively or not.   | ||||||
|  |   **DANGER ZONE:** This setting has the potential to create "an isolated island", which can have unexpected side effects | ||||||
|  |   and break invites in rooms. This will most likely not have the effect you think it does. Only change the value if you | ||||||
|  |   understand the consequences. | ||||||
|  |  | ||||||
|  | `invite.resolution.timer`   | ||||||
|  | - Default value: `1`   | ||||||
|  | - Description: How often, in minutes, mxisd should try to resolve pending invites. | ||||||
|  |  | ||||||
|  | ## 3PID addition to user profile | ||||||
|  | See the [3PID session documents](../threepids/session) | ||||||
							
								
								
									
										141
									
								
								docs/getting-started.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								docs/getting-started.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | |||||||
|  | # Getting started | ||||||
|  | 1. [Preparation](#preparation) | ||||||
|  | 2. [Install](#install) | ||||||
|  | 3. [Configure](#configure) | ||||||
|  | 4. [Integrate](#integrate) | ||||||
|  | 5. [Validate](#validate) | ||||||
|  | 6. [Next steps](#next-steps) | ||||||
|  |  | ||||||
|  | Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups and | ||||||
|  | talk to the central Matrix.org Identity server.   | ||||||
|  | This will be a good ground work for further integration with features and your existing Identity stores. | ||||||
|  |  | ||||||
|  | ## Preparation | ||||||
|  | You will need: | ||||||
|  | - Working Homeserver, ideally with working federation | ||||||
|  | - Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain | ||||||
|  |  | ||||||
|  | As synapse requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as mxisd does | ||||||
|  | not support HTTPS listener at this time. | ||||||
|  |  | ||||||
|  | For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname. | ||||||
|  |  | ||||||
|  | Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-matrix/mxisd/wiki/Gotchas#nating) if you use the same | ||||||
|  | hostname. | ||||||
|  |  | ||||||
|  | The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.   | ||||||
|  | If you would like a high-level view of the infrastructure and how each feature is integrated, see the | ||||||
|  | [dedicated document](architecture.md) | ||||||
|  |  | ||||||
|  | ## Install | ||||||
|  | Install via: | ||||||
|  | - [Debian package](install/debian.md) | ||||||
|  | - [ArchLinux](install/archlinux.md) | ||||||
|  | - [Docker image](install/docker.md) | ||||||
|  | - [Sources](build.md) | ||||||
|  |  | ||||||
|  | See the [Latest release](https://github.com/kamax-matrix/mxisd/releases/latest) for links to each. | ||||||
|  |  | ||||||
|  | ## Configure | ||||||
|  | **NOTE**: please view the install instruction for your platform, as this step might be optional or already handled for you. | ||||||
|  |  | ||||||
|  | Create/edit a minimal configuration (see installer doc for the location): | ||||||
|  | ```yaml | ||||||
|  | matrix.domain: 'example.org' | ||||||
|  | key.path: '/path/to/signing.key.file' | ||||||
|  | storage.provider.sqlite.database: '/path/to/mxisd.db' | ||||||
|  | ```   | ||||||
|  | - `matrix.domain` should be set to your Homeserver domain (`server_name` in synapse configuration) | ||||||
|  | - `key.path` will store the signing keys, which must be kept safe! If the file does not exist, keys will be generated for you. | ||||||
|  | - `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.) | ||||||
|  |  | ||||||
|  | If your HS/mxisd hostname is not the same as your Matrix domain, configure `server.name`.   | ||||||
|  | Complete configuration guide is available [here](configure.md). | ||||||
|  |  | ||||||
|  | ## Integrate | ||||||
|  | For an overview of a typical mxisd infrastructure, see the [dedicated document](architecture.md) | ||||||
|  | ### Reverse proxy | ||||||
|  | #### Apache2 | ||||||
|  | In the `VirtualHost` section handling the domain with SSL, add the following and replace `0.0.0.0` by the internal | ||||||
|  | hostname/IP pointing to mxisd.   | ||||||
|  | **This line MUST be present before the one for the homeserver!** | ||||||
|  | ```apache | ||||||
|  | ProxyPass /_matrix/identity http://0.0.0.0:8090/_matrix/identity | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Typical configuration would look like: | ||||||
|  | ```apache | ||||||
|  | <VirtualHost *:443> | ||||||
|  |     ServerName example.org | ||||||
|  |      | ||||||
|  |     ... | ||||||
|  |      | ||||||
|  |     ProxyPreserveHost on | ||||||
|  |     ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity | ||||||
|  |     ProxyPass /_matrix http://localhost:8008/_matrix | ||||||
|  | </VirtualHost> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### nginx | ||||||
|  | In the `server` section handling the domain with SSL, add the following and replace `0.0.0.0` with the internal | ||||||
|  | hostname/IP pointing to mxisd. | ||||||
|  | **This line MUST be present before the one for the homeserver!** | ||||||
|  | ```nginx | ||||||
|  | location /_matrix/identity { | ||||||
|  |     proxy_pass http://0.0.0.0:8090/_matrix/identity; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Typical configuration would look like: | ||||||
|  | ```nginx | ||||||
|  | server { | ||||||
|  |     listen 443 ssl; | ||||||
|  |     server_name example.org; | ||||||
|  |      | ||||||
|  |     ... | ||||||
|  |      | ||||||
|  |     location /_matrix/identity { | ||||||
|  |         proxy_pass http://localhost:8090/_matrix/identity; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     location /_matrix { | ||||||
|  |         proxy_pass http://localhost:8008/_matrix; | ||||||
|  |         proxy_set_header Host $host; | ||||||
|  |         proxy_set_header X-Forwarded-For $remote_addr; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Synapse | ||||||
|  | Add your mxisd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.   | ||||||
|  | In a typical configuration, you would end up with something similar to: | ||||||
|  | ```yaml | ||||||
|  | trusted_third_party_id_servers: | ||||||
|  |     - example.org | ||||||
|  | ``` | ||||||
|  | It is recommended to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration so only | ||||||
|  | your own Identity server is authoritative for your HS. | ||||||
|  |  | ||||||
|  | ## Validate | ||||||
|  | **NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider | ||||||
|  | your installation validated. | ||||||
|  |  | ||||||
|  | 1. Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by | ||||||
|  | the relevant hostname which you configured in your reverse proxy. | ||||||
|  | 2. Create a new empty room. All further actions will take place in this room. | ||||||
|  | 3. Invite `mxisd-federation-test@kamax.io` | ||||||
|  | 4. The 3PID invite should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`. | ||||||
|  | 5. The invited test user will join the room, send a congratulation message and leave. | ||||||
|  | **NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite. | ||||||
|  |    | ||||||
|  | If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!   | ||||||
|  | If it did not work, [get in touch](../README.md#support) and we'll do our best to get you started. | ||||||
|  |  | ||||||
|  | ## Next steps | ||||||
|  | Once your mxisd server is up and running, there are several ways you can enhance and integrate further with your | ||||||
|  | infrastructure: | ||||||
|  |  | ||||||
|  | - [Enable extra features](features/) | ||||||
|  | - [Use your own Identity stores](stores/README.md) | ||||||
							
								
								
									
										3
									
								
								docs/install/archlinux.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/install/archlinux.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | # Arch Linux package | ||||||
|  | An Arch Linux package in the AUR repos is maintained by [r3pek](https://matrix.to/#/@r3pek:r3pek.org), a community member.   | ||||||
|  | See https://aur.archlinux.org/packages/mxisd/ | ||||||
							
								
								
									
										42
									
								
								docs/install/debian.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								docs/install/debian.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | # 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: | ||||||
|  | ```bash | ||||||
|  | dpkg -i /path/to/downloaded/mxisd.deb | ||||||
|  | ``` | ||||||
|  | ## Files | ||||||
|  | | Location                            | Purpose                                      | | ||||||
|  | |-------------------------------------|----------------------------------------------| | ||||||
|  | | `/etc/mxisd`                        | Configuration directory                      | | ||||||
|  | | `/etc/mxisd/mxisd.yaml`             | Main configuration file                      | | ||||||
|  | | `/etc/systemd/system/mxisd.service` | Systemd configuration file for mxisd service | | ||||||
|  | | `/usr/lib/mxisd`                    | Binaries                                     | | ||||||
|  | | `/var/lib/mxisd`                    | Data                                         | | ||||||
|  | | `/var/lib/mxisd/signing.key`        | Default location for mxisd signing keys      | | ||||||
|  |  | ||||||
|  | ## Control | ||||||
|  | Start mxisd using: | ||||||
|  | ```bash | ||||||
|  | sudo systemctl start mxisd | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Stop mxisd using: | ||||||
|  | ```bash | ||||||
|  | sudo systemctl stop mxisd | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Troubleshoot | ||||||
|  | All logs are sent to `STDOUT` which are saved in `/var/log/syslog` by default.   | ||||||
|  | You can: | ||||||
|  | - grep & tail using `mxisd`: | ||||||
|  | ``` | ||||||
|  | tail -n 99 -f /var/log/syslog | grep mxisd | ||||||
|  | ``` | ||||||
|  | - use Systemd's journal: | ||||||
|  | ``` | ||||||
|  | journalctl -f -n 99 -u mxisd | ||||||
|  | ``` | ||||||
							
								
								
									
										22
									
								
								docs/install/docker.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								docs/install/docker.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | # Docker | ||||||
|  | ## Fetch | ||||||
|  | Pull the latest stable image: | ||||||
|  | ```bash | ||||||
|  | docker pull kamax/mxisd | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Configure | ||||||
|  | On first run, simply using `MATRIX_DOMAIN` as an environment variable will create a default config for you.   | ||||||
|  | You can also provide a configuration file named `mxisd.yaml` in the volume mapped to `/etc/mxisd` before starting your | ||||||
|  | container. | ||||||
|  |  | ||||||
|  | ## Run | ||||||
|  | Use the following command after adapting to your needs: | ||||||
|  | - The `MATRIX_DOMAIN` environment variable to yours | ||||||
|  | - The volumes host paths | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | docker run --rm -e MATRIX_DOMAIN=example.org -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/) | ||||||
							
								
								
									
										38
									
								
								docs/install/source.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								docs/install/source.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | # Install from sources | ||||||
|  | ## Instructions | ||||||
|  | Follow the [build instructions](../build.md) then: | ||||||
|  |  | ||||||
|  | 1. Prepare files and directories: | ||||||
|  | ```bash | ||||||
|  | # Create a dedicated user | ||||||
|  | useradd -r mxisd | ||||||
|  |  | ||||||
|  | # Create bin directory | ||||||
|  | mkdir /opt/mxisd | ||||||
|  |  | ||||||
|  | # Create config directory and set ownership | ||||||
|  | mkdir -p /etc/opt/mxisd | ||||||
|  | chown -R mxisd /etc/opt/mxisd | ||||||
|  |  | ||||||
|  | # Create data directory and set ownership | ||||||
|  | mkdir -p /var/opt/mxisd | ||||||
|  | chown -R mxisd /var/opt/mxisd | ||||||
|  |  | ||||||
|  | # Copy <repo root>/build/libs/mxisd.jar to bin directory | ||||||
|  | cp ./build/libs/mxisd.jar /opt/mxisd/ | ||||||
|  | chown mxisd /opt/mxisd/mxisd.jar | ||||||
|  | chmod a+x /opt/mxisd/mxisd.jar | ||||||
|  |  | ||||||
|  | # Create symlink for easy exec | ||||||
|  | ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd | ||||||
|  | ``` | ||||||
|  | 2. Copy the sample config file `./application.example.yaml` to `/etc/opt/mxisd/mxisd.yaml`, edit to your needs | ||||||
|  | 3. Copy `src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed | ||||||
|  | 4. Enable service for auto-startup | ||||||
|  | ```bash | ||||||
|  | systemctl enable mxisd | ||||||
|  | ``` | ||||||
|  | 5. Start mxisd | ||||||
|  | ```bash | ||||||
|  | systemctl start mxisd | ||||||
|  | ``` | ||||||
							
								
								
									
										8
									
								
								docs/stores/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/stores/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # Identity Stores | ||||||
|  | - [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!) | ||||||
							
								
								
									
										60
									
								
								docs/stores/exec.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								docs/stores/exec.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | # Exec Identity Store | ||||||
|  | This Identity Store lets you run arbitrary commands to handle the various requests in each support feature. | ||||||
|  |  | ||||||
|  | This is the most versatile Identity store of mxisd, allowing you to connect any kind of logic in any language/scripting. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      |   Supported?  | | ||||||
|  | |----------------|---------------| | ||||||
|  | | Authentication | Yes           | | ||||||
|  | | Directory      | *In Progress* | | ||||||
|  | | Identity       | *In Progress* | | ||||||
|  | | Profile        | *In Progress* | | ||||||
|  |  | ||||||
|  | ## Overview | ||||||
|  | Each request can be mapping to a fully customizable command configuration.   | ||||||
|  | The various parameters can be provided via any combination of: | ||||||
|  | - Standard Input | ||||||
|  | - Command line arguments | ||||||
|  | - Environment variables | ||||||
|  |  | ||||||
|  | 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 [Exit status](https://en.wikipedia.org/wiki/Exit_status) and Standard Output, both | ||||||
|  | supporting a set of options.  | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ```yaml | ||||||
|  | exec.enabled: <boolean> | ||||||
|  | ``` | ||||||
|  | Enable/disable the Identity store at a global/default level. Each feature can still be enabled/disabled specifically. | ||||||
|  |  | ||||||
|  | *TBC* | ||||||
|  |  | ||||||
|  | ## Use-case examples | ||||||
|  | ```yaml | ||||||
|  | exec.enabled: true | ||||||
|  |  | ||||||
|  | exec.auth.command: '/path/to/auth/executable' | ||||||
|  | exec.auth.args: ['-u', '{localpart}'] | ||||||
|  | exec.auth.env: | ||||||
|  |   PASSWORD: '{password}' | ||||||
|  |   MATRIX_DOMAIN: '{domain}' | ||||||
|  |   MATRIX_USER_ID: '{mxid}' | ||||||
|  | ``` | ||||||
|  | This will run `/path/to/auth/executable` with: | ||||||
|  | - The extracted Matrix User ID `localpart` provided as the second command line argument, the first one being `-u`  | ||||||
|  | - The password, the extract Matrix `domain` and the full User ID as arbitrary environment variables, respectively `PASSWORD`, `MATRIX_DOMAIN` and `MATRIX_USER_ID` | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | ## Few more available config items | ||||||
|  | # | ||||||
|  | # exec.token.domain: '{matrixDomain}' # This sets the default replacement token for the Matrix Domain of the User ID, across all features. | ||||||
|  | # exec.auth.token.domain: '{matrixDomainForAuth}' # We can also set another token specific to a feature. | ||||||
|  | # exec.auth.input: 'json' # This is not supported yet. | ||||||
|  | # exec.auth.exit.success: [0] # Exit status that will consider the request successful. This is already the default. | ||||||
|  | # exec.auth.exit.failure: [1,2,3] # Exist status that will consider the request failed. Anything else than success or failure statuses will throw an exception. | ||||||
|  | # exec.auth.output: 'json' # Required if stdout should be read on success. This uses the same output as the REST Identity store for Auth. | ||||||
|  | ``` | ||||||
|  | *TBC* | ||||||
							
								
								
									
										53
									
								
								docs/stores/firebase.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								docs/stores/firebase.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | # Google Firebase Identity store | ||||||
|  | https://firebase.google.com/ | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | Yes        | | ||||||
|  | | Directory      | No         | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | No         | | ||||||
|  |  | ||||||
|  | ## Requirements | ||||||
|  | This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following | ||||||
|  | information: | ||||||
|  | - Firebase User ID as Matrix username | ||||||
|  | - Firebase token as Matrix password | ||||||
|  |  | ||||||
|  | If your client is Riot, you will need a custom version. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ```yaml | ||||||
|  | firebase.enabled: <boolean> | ||||||
|  | ``` | ||||||
|  | Enable/disable this identity store. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | firebase.enabled: <boolean> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | firebase.credentials: <string> | ||||||
|  | ``` | ||||||
|  | Path to the credentials file provided by Google Firebase to use with an external app. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | firebase.credentials: '/path/to/firebase/credentials.json' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | firebase.database: <string> | ||||||
|  | ``` | ||||||
|  | URL to your Firebase database. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | firebase.database: 'https://my-project.firebaseio.com/' | ||||||
|  | ``` | ||||||
							
								
								
									
										121
									
								
								docs/stores/ldap.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								docs/stores/ldap.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | # LDAP Identity store | ||||||
|  | ## Supported products: | ||||||
|  | - Samba | ||||||
|  | - Active Directory | ||||||
|  | - OpenLDAP | ||||||
|  | - NetIQ eDirectory | ||||||
|  |  | ||||||
|  | For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | Yes        | | ||||||
|  | | Directory      | Yes        | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | Yes        | | ||||||
|  |  | ||||||
|  | ## Getting started | ||||||
|  | ### Base | ||||||
|  | To use your LDAP backend, add the bare minimum configuration in mxisd config file: | ||||||
|  | ```yaml | ||||||
|  | ldap.enabled: true | ||||||
|  | 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' | ||||||
|  | ``` | ||||||
|  | These are standard LDAP connection configuration. mxisd will try to connect on port default port 389 without encryption. | ||||||
|  |  | ||||||
|  | ### TLS/SSL connection | ||||||
|  | If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported): | ||||||
|  | ```yaml | ||||||
|  | ldap.connection.tls: true | ||||||
|  | ldap.connection.port: 12345 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Filter results | ||||||
|  | You can also set a default global filter on any LDAP queries: | ||||||
|  | ```yaml | ||||||
|  | ldap.filter: '(memberOf=CN=My Matrix Users,OU=Groups,DC=example,DC=org)' | ||||||
|  | ``` | ||||||
|  | This example would only return users part of the group called `My Matrix Users`. | ||||||
|  | This can be overwritten or append in each specific flow describe below. | ||||||
|  |  | ||||||
|  | For supported syntax, see the [LDAP library documentation](http://directory.apache.org/api/user-guide/2.3-searching.html#filter). | ||||||
|  |  | ||||||
|  | ### Attribute mapping | ||||||
|  | LDAP features are based on mapping LDAP attributes to Matrix concepts, like a Matrix ID, its localpart, the user display | ||||||
|  | name, their email(s) and/or phone number(s). | ||||||
|  |       | ||||||
|  | Default attributes are well suited for Active Directory/Samba. In case you are using a native LDAP backend, you will | ||||||
|  | most certainly configure those mappings. | ||||||
|  |  | ||||||
|  | #### User ID | ||||||
|  | `ldap.attribute.uid.type`: How to process the User ID (UID) attribute: | ||||||
|  | - `uid` will consider the value as the [Localpart](https://matrix.org/docs/spec/intro.html#user-identifiers) | ||||||
|  | - `mxid` will consider the value as a complete [Matrix ID](https://matrix.org/docs/spec/intro.html#user-identifiers) | ||||||
|  |  | ||||||
|  | `ldap.attribute.uid.value`: Attribute to use to set the User ID value. | ||||||
|  |  | ||||||
|  | The following example would set the `sAMAccountName` attribute as a Matrix User ID localpart: | ||||||
|  | ```yaml | ||||||
|  | ldap.attribute.uid.type: 'uid' | ||||||
|  | ldap.attribute.uid.value: 'sAMAccountName' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Display name | ||||||
|  | Use `ldap.attribute.name`. | ||||||
|  |  | ||||||
|  | The following example would set the display name to the value of the `cn` attribute: | ||||||
|  | ```yaml | ||||||
|  | ldap.attribute.name: 'cn' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### 3PIDs | ||||||
|  | You can also change the attribute lists for 3PID, like email or phone numbers. | ||||||
|  |  | ||||||
|  | The following example would overwrite the [default list of attributes](../../src/main/resources/application.yaml#L67) | ||||||
|  | for emails and phone number: | ||||||
|  | ```yaml | ||||||
|  | ldap.attribute.threepid.email: | ||||||
|  |   - 'mail' | ||||||
|  |   - 'otherMailAttribute' | ||||||
|  |  | ||||||
|  | ldap.attribute.threepid.msisdn: | ||||||
|  |   - 'phone' | ||||||
|  |   - 'otherPhoneAttribute' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | ### Identity | ||||||
|  | Identity features (related to 3PID invites or searches) are enabled and configured using default values and no specific | ||||||
|  | configuration item is needed to get started. | ||||||
|  |  | ||||||
|  | #### Configuration | ||||||
|  | - `ldap.identity.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set. | ||||||
|  | - `ldap.identity.medium`: Namespace to overwrite generated queries from the list of attributes for each 3PID medium. | ||||||
|  |  | ||||||
|  | ### Authentication | ||||||
|  | No further configuration is needed to use the Authentication feature with LDAP once globally enabled and configured. | ||||||
|  |  | ||||||
|  | Profile auto-fill is enabled by default. It will use the `ldap.attribute.name` and `ldap.attribute.threepid` configuration | ||||||
|  | options to get a lit of attributes to be used to build the user profile to pass on to synapse during authentication. | ||||||
|  |  | ||||||
|  | #### Configuration | ||||||
|  | - `ldap.auth.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set. | ||||||
|  |  | ||||||
|  | ### Directory | ||||||
|  | No further configuration is needed to use the Directory feature with LDAP once globally enabled and configured. | ||||||
|  |  | ||||||
|  | #### Configuration | ||||||
|  | To set a specific filter applied during directory search, use `ldap.directory.filter` | ||||||
|  |  | ||||||
|  | If you would like to use extra attributes in search that are not 3PIDs, like nicknames, group names, employee number: | ||||||
|  | ```yaml | ||||||
|  | ldap.directory.attribute.other: | ||||||
|  |   - 'myNicknameAttribute' | ||||||
|  |   - 'memberOf' | ||||||
|  |   - 'employeeNumberAttribute' | ||||||
|  | ``` | ||||||
							
								
								
									
										217
									
								
								docs/stores/rest.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								docs/stores/rest.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | |||||||
|  | # REST Identity store | ||||||
|  | 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, ...) | ||||||
|  |  | ||||||
|  | To integrate this backend with your webapp, you will need to implement three specific REST endpoints detailed below. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | Yes        | | ||||||
|  | | Directory      | Yes        | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | No         | | ||||||
|  |  | ||||||
|  | ## 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                     | | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  | `rest.host` is mandatory if at least one endpoint is not a full URL. | ||||||
|  |  | ||||||
|  | ## Endpoints | ||||||
|  | ### Authentication | ||||||
|  | HTTP method: `POST`   | ||||||
|  | Content-type: JSON UTF-8 | ||||||
|  |    | ||||||
|  | #### Request Body | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "auth": { | ||||||
|  |     "mxid": "@john.doe:example.org", | ||||||
|  |     "localpart": "john.doe", | ||||||
|  |     "domain": "example.org", | ||||||
|  |     "password": "passwordOfTheUser" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Response Body | ||||||
|  | If the authentication fails: | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "auth": { | ||||||
|  |     "success": false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If the authentication succeed: | ||||||
|  | - `auth.id` supported values: `localpart`, `mxid` | ||||||
|  | - `auth.profile` and any sub-member are all optional | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "auth": { | ||||||
|  |     "success": true, | ||||||
|  |     "id": { | ||||||
|  |       "type": "localpart", | ||||||
|  |       "value": "john" | ||||||
|  |     }, | ||||||
|  |     "profile": { | ||||||
|  |       "display_name": "John Doe", | ||||||
|  |       "three_pids": [ | ||||||
|  |         { | ||||||
|  |           "medium": "email", | ||||||
|  |           "address": "john.doe@example.org" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "medium": "msisdn", | ||||||
|  |           "address": "123456789" | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Directory | ||||||
|  | HTTP method: `POST` | ||||||
|  | Content-type: JSON UTF-8 | ||||||
|  |  | ||||||
|  | #### Request Body | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "by": "<search type>", | ||||||
|  |   "search_term": "doe" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | `by` can be: | ||||||
|  | - `name` | ||||||
|  | - `threepid` | ||||||
|  |  | ||||||
|  | #### Response Body: | ||||||
|  | If users found: | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "limited": false, | ||||||
|  |   "results": [ | ||||||
|  |     { | ||||||
|  |       "avatar_url": "http://domain.tld/path/to/avatar.png", | ||||||
|  |       "display_name": "John Doe", | ||||||
|  |       "user_id": "UserIdLocalpart" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       ... | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If no user found: | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "limited": false, | ||||||
|  |   "results": [] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Identity | ||||||
|  | #### Single 3PID lookup | ||||||
|  | HTTP method: `POST`   | ||||||
|  | Content-type: JSON UTF-8   | ||||||
|  |    | ||||||
|  | #### Request Body | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "lookup": { | ||||||
|  |     "medium": "email", | ||||||
|  |     "address": "john.doe@example.org" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Response Body | ||||||
|  | If a match was found: | ||||||
|  | - `lookup.id.type` supported values: `localpart`, `mxid` | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "lookup": { | ||||||
|  |     "medium": "email", | ||||||
|  |     "address": "john.doe@example.org", | ||||||
|  |     "id": { | ||||||
|  |       "type": "mxid", | ||||||
|  |       "value": "@john:example.org" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If no match was found: | ||||||
|  | ```json | ||||||
|  | {} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Bulk 3PID lookup | ||||||
|  | HTTP method: `POST`   | ||||||
|  | Content-type: JSON UTF-8   | ||||||
|  |    | ||||||
|  | #### Request Body | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "lookup": [ | ||||||
|  |     { | ||||||
|  |       "medium": "email", | ||||||
|  |       "address": "john.doe@example.org" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "medium": "msisdn", | ||||||
|  |       "address": "123456789" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Response Body | ||||||
|  | For all entries where a match was found: | ||||||
|  | - `lookup[].id.type` supported values: `localpart`, `mxid` | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "lookup": [ | ||||||
|  |     { | ||||||
|  |       "medium": "email", | ||||||
|  |       "address": "john.doe@example.org", | ||||||
|  |       "id": { | ||||||
|  |         "type": "localpart", | ||||||
|  |         "value": "john" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "medium": "msisdn", | ||||||
|  |       "address": "123456789", | ||||||
|  |       "id": { | ||||||
|  |         "type": "mxid", | ||||||
|  |         "value": "@jane:example.org" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If no match was found: | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "lookup": [] | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										99
									
								
								docs/stores/sql.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								docs/stores/sql.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | # SQL Identity store | ||||||
|  | ## Supported Databases | ||||||
|  | - PostgreSQL | ||||||
|  | - MariaDB | ||||||
|  | - MySQL | ||||||
|  | - SQLite | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | No         | | ||||||
|  | | Directory      | Yes        | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | Yes        | | ||||||
|  |  | ||||||
|  | Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication | ||||||
|  | will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically | ||||||
|  | the [REST Identity store](rest.md). | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ### Basic | ||||||
|  | ```yaml | ||||||
|  | sql.enabled: <boolean> | ||||||
|  | ``` | ||||||
|  | Enable/disable the identity store | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | sql.type: <string> | ||||||
|  | ``` | ||||||
|  | Set the SQL backend to use: | ||||||
|  | - `sqlite` | ||||||
|  | - `postgresql` | ||||||
|  | - `mariadb` | ||||||
|  | - `mysql` | ||||||
|  |  | ||||||
|  | ### Connection | ||||||
|  | #### SQLite | ||||||
|  | ```yaml | ||||||
|  | sql.connection: <string> | ||||||
|  | ``` | ||||||
|  | Set the value to the absolute path to the Synapse SQLite DB file. | ||||||
|  | Example: `/path/to/sqlite/file.db` | ||||||
|  |  | ||||||
|  | #### Others | ||||||
|  | ```yaml | ||||||
|  | sql.connection: //<HOST[:PORT]/DB?user=USER&password=PASS | ||||||
|  | ``` | ||||||
|  | Set the connection info for the database by replacing the following values: | ||||||
|  | - `HOST`: Hostname of the SQL server | ||||||
|  | - `PORT`: Optional port value, if not default | ||||||
|  | - `DB`: Database name | ||||||
|  | - `USER`: Username for the connection | ||||||
|  | - `PASS`: Password for the connection | ||||||
|  |  | ||||||
|  | This follow the JDBC URI syntax. See [official website](https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html#db_connection_url). | ||||||
|  |  | ||||||
|  | ### Directory | ||||||
|  | ```yaml | ||||||
|  | sql.directory.enabled: false | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | sql.directory.query: | ||||||
|  |   name: | ||||||
|  |     type: <string> | ||||||
|  |     value: <string> | ||||||
|  |   threepid: | ||||||
|  |     type: <string> | ||||||
|  |     value: <string> | ||||||
|  | ``` | ||||||
|  | For each query, `type` can be used to tell mxisd how to process the ID column: | ||||||
|  | - `localpart` will append the `matrix.domain` to it | ||||||
|  | - `mxid` will use the ID as-is. If it is not a valid Matrix ID, the search will fail. | ||||||
|  |  | ||||||
|  | `value` is the SQL query and must return two columns: | ||||||
|  | - The first being the User ID | ||||||
|  | - The second being its display name | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  | ```yaml | ||||||
|  | sql.directory.query: | ||||||
|  |   name: | ||||||
|  |     type: 'localpart' | ||||||
|  |     value: 'SELECT idColumn, displayNameColumn FROM table WHERE displayNameColumn LIKE ?' | ||||||
|  |   threepid: | ||||||
|  |     type: 'localpart' | ||||||
|  |     value: 'SELECT idColumn, displayNameColumn FROM table WHERE threepidColumn LIKE ?' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Identity | ||||||
|  | ```yaml | ||||||
|  | sql.identity.type: <string> | ||||||
|  | sql.identity.query: <string> | ||||||
|  | ``` | ||||||
							
								
								
									
										49
									
								
								docs/stores/synapse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/stores/synapse.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | # Synapse Identity Store | ||||||
|  | Synapse's Database itself can be used as an Identity store. | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | No         | | ||||||
|  | | Directory      | Yes        | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | Yes        | | ||||||
|  |  | ||||||
|  | Authentication is done by Synapse itself. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ### Basic | ||||||
|  | ```yaml | ||||||
|  | synapseSql.enabled: <boolean> | ||||||
|  | ``` | ||||||
|  | Enable/disable the identity store | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | synapseSql.type: <string> | ||||||
|  | ``` | ||||||
|  | Set the SQL backend to use which is configured in synapse: | ||||||
|  | - `sqlite` | ||||||
|  | - `postgresql` | ||||||
|  |  | ||||||
|  | ### SQLite | ||||||
|  | ```yaml | ||||||
|  | synapseSql.connection: <string> | ||||||
|  | ``` | ||||||
|  | Set the value to the absolute path to the Synapse SQLite DB file. | ||||||
|  | Example: `/path/to/synapse/sqliteFile.db` | ||||||
|  |  | ||||||
|  | ### PostgreSQL | ||||||
|  | ```yaml | ||||||
|  | synapseSql.connection: //<HOST[:PORT]/DB?user=USER&password=PASS | ||||||
|  | ``` | ||||||
|  | Set the connection info for the database by replacing the following values: | ||||||
|  | - `HOST`: Hostname of the SQL server | ||||||
|  | - `PORT`: Optional port value, if not default | ||||||
|  | - `DB`: Database name | ||||||
|  | - `USER`: Username for the connection | ||||||
|  | - `PASS`: Password for the connection | ||||||
|  |  | ||||||
|  | ### Query customization | ||||||
|  | See the [SQL Identity store](sql.md) | ||||||
							
								
								
									
										66
									
								
								docs/stores/wordpress.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								docs/stores/wordpress.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | # Wordpress Identity store | ||||||
|  | This Identity store allows you to use user accounts registered on your Wordpress setup.   | ||||||
|  | Two types of connections are required for full support: | ||||||
|  | - [REST API](https://developer.wordpress.org/rest-api/) with JWT authentication | ||||||
|  | - Direct SQL access | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  | |      Name      | Supported? | | ||||||
|  | |----------------|------------| | ||||||
|  | | Authentication | Yes        | | ||||||
|  | | Directory      | Yes        | | ||||||
|  | | Identity       | Yes        | | ||||||
|  | | Profile        | No         | | ||||||
|  |  | ||||||
|  | ## Requirements | ||||||
|  | - [Wordpress](https://wordpress.org/download/) >= 4.4 | ||||||
|  | - Permalink structure set to `Post Name` | ||||||
|  | - [JWT Auth plugin for REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/) | ||||||
|  | - SQL Credentials to the Wordpress Database | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ### Wordpress | ||||||
|  | #### JWT Auth | ||||||
|  | Set a JWT secret into `wp-config.php` like so: | ||||||
|  | ```php | ||||||
|  | define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key'); | ||||||
|  | ``` | ||||||
|  | `your-top-secret-key` should be set to a randomly generated value which is kept secret. | ||||||
|  |  | ||||||
|  | #### Rewrite of `index.php` | ||||||
|  | Wordpress is normally configured with rewrite of `index.php` so it does not appear in URLs.   | ||||||
|  | If this is not the case for your installation, the mxisd URL will need to be appended with `/index.php` | ||||||
|  |  | ||||||
|  | ### mxisd | ||||||
|  | Enable in the configuration: | ||||||
|  | ```yaml | ||||||
|  | wordpress.enabled: true | ||||||
|  | ``` | ||||||
|  | Configure the URL to your Wordpress installation - see above about added `/index.php`: | ||||||
|  | ```yaml | ||||||
|  | wordpress.rest.base: 'http://localhost:8080' | ||||||
|  | ``` | ||||||
|  | Configure the SQL connection to your Wordpress database: | ||||||
|  | ```yaml | ||||||
|  | wordpress.sql.connection: '//127.0.0.1/wordpress?user=root&password=example' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | By default, MySQL database is expected. If you use another database, use: | ||||||
|  | ```yaml | ||||||
|  | wordpress.sql.type: <string> | ||||||
|  | ``` | ||||||
|  | With possible values: | ||||||
|  | - `mysql` | ||||||
|  | - `mariadb` | ||||||
|  | - `postgresql` | ||||||
|  | - `sqlite` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | To configure the tables prefix for default queries, in case a custom value was set during Wordpress install: | ||||||
|  | ```yaml | ||||||
|  | wordpress.sql.tablePrefix: <string> | ||||||
|  | ``` | ||||||
|  | By default, the value is set to `wp_`. | ||||||
							
								
								
									
										19
									
								
								docs/threepids/medium/email/smtp-connector.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/threepids/medium/email/smtp-connector.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # Email notifications - SMTP connector | ||||||
|  | Enabled by default. | ||||||
|  |  | ||||||
|  | Connector ID: `smtp` | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ```yaml | ||||||
|  | threepid.medium.email: | ||||||
|  |   identity: | ||||||
|  |     from: 'identityServerEmail@example.org' | ||||||
|  |     name: 'My Identity Server' | ||||||
|  |   connectors: | ||||||
|  |     smtp: | ||||||
|  |       host: 'smtpHostname' | ||||||
|  |       port: 587 | ||||||
|  |       tls: 1 # 0 = no STARTLS, 1 = try, 2 = force | ||||||
|  |       login: 'smtpLogin' | ||||||
|  |       password: 'smtpPassword' | ||||||
|  | ``` | ||||||
							
								
								
									
										11
									
								
								docs/threepids/medium/msisdn/twilio-connector.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/threepids/medium/msisdn/twilio-connector.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # SMS notifications - Twilio connector | ||||||
|  | Enabled by default. | ||||||
|  |  | ||||||
|  | Connector ID: `twilio` | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | ```yaml | ||||||
|  | threepid.medium.msisdn.connectors.twilio.accountSid: 'myAccountSid' | ||||||
|  | threepid.medium.msisdn.connectors.twilio.authToken: 'myAuthToken' | ||||||
|  | threepid.medium.msisdn.connectors.twilio.number: '+123456789' | ||||||
|  | ``` | ||||||
							
								
								
									
										37
									
								
								docs/threepids/notification/basic-handler.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								docs/threepids/notification/basic-handler.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | # Basic Notification handler | ||||||
|  | Basic notification handler which uses two components: | ||||||
|  | - Content generator, to produce the notifications | ||||||
|  | - Connectors to send the notification content | ||||||
|  |  | ||||||
|  | This handler can be used with the 3PID types: | ||||||
|  | - `email` | ||||||
|  | - `msisdn` (Phone numbers) | ||||||
|  |  | ||||||
|  | ## Generators | ||||||
|  | - [Template](template-generator.md) | ||||||
|  | ## Connectors | ||||||
|  | - Email | ||||||
|  |   - [SMTP](../medium/email/smtp-connector.md) | ||||||
|  | - SMS | ||||||
|  |   - [Twilio](../medium/msisdn/twilio-connector.md) | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | Enabled by default or with: | ||||||
|  | ```yaml | ||||||
|  | notification.handler.email: 'raw' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **WARNING:** Will be consolidated soon, prone to breaking changes.   | ||||||
|  | Structure and default values: | ||||||
|  | ```yaml | ||||||
|  | threepid.medium: | ||||||
|  |   email: | ||||||
|  |     identity: | ||||||
|  |       from: '' | ||||||
|  |       name: '' | ||||||
|  |     connector: 'smtp' | ||||||
|  |     generator: 'template' | ||||||
|  |   msisdn: | ||||||
|  |     connector: 'twilio' | ||||||
|  |     generator: 'template' | ||||||
|  | ``` | ||||||
							
								
								
									
										7
									
								
								docs/threepids/notification/sendgrid-handler.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/threepids/notification/sendgrid-handler.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | # SendGrid Notification handler | ||||||
|  | To be completed. See [raw possible configuration items](https://github.com/kamax-matrix/mxisd/blob/master/src/main/resources/application.yaml#L172). | ||||||
|  |  | ||||||
|  | Enabled with: | ||||||
|  | ```yaml | ||||||
|  | notification.handler.email: 'sendgrid' | ||||||
|  | ``` | ||||||
							
								
								
									
										73
									
								
								docs/threepids/notification/template-generator.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								docs/threepids/notification/template-generator.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | # Notifications: Generate from templates | ||||||
|  | To create notification content, you can use the `template` generator if supported for the 3PID medium which will read | ||||||
|  | content from configured files. | ||||||
|  |  | ||||||
|  | Placeholders can be integrated into the templates to dynamically populate such content with relevant information like | ||||||
|  | the 3PID that was requested, the domain of your Identity server, etc. | ||||||
|  |  | ||||||
|  | Templates can be configured for each event that would send a notification to the end user. Events share a set of common | ||||||
|  | placeholders and also have their own individual set of placeholders. | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | To configure paths to the various templates: | ||||||
|  | ```yaml | ||||||
|  | threepid.medium.<YOUR 3PID MEDIUM HERE>: | ||||||
|  |   generators: | ||||||
|  |     template: | ||||||
|  |       invite: '/path/to/invite-template.eml' | ||||||
|  |       session: | ||||||
|  |         validation: | ||||||
|  |           local: '/path/to/validate-local-template.eml' | ||||||
|  |           remote: 'path/to/validate-remote-template.eml' | ||||||
|  |       generic: | ||||||
|  |         matrixId: '/path/to/mxid-invite-template.eml' | ||||||
|  | ``` | ||||||
|  | The `template` generator is usually the default, so no further configuration is needed. | ||||||
|  |  | ||||||
|  | ##  Global placeholders | ||||||
|  | | Placeholder           | Purpose                                                                      | | ||||||
|  | |-----------------------|------------------------------------------------------------------------------| | ||||||
|  | | `%DOMAIN%`            | Identity server authoritative domain, as configured in `matrix.domain`       | | ||||||
|  | | `%DOMAIN_PRETTY%`     | Same as `%DOMAIN%` with the first letter upper case and all other lower case | | ||||||
|  | | `%FROM_EMAIL%`        | Email address configured in `threepid.medium.<3PID medium>.identity.from`    | | ||||||
|  | | `%FROM_NAME%`         | Name configured in `threepid.medium.<3PID medium>.identity.name`             | | ||||||
|  | | `%RECIPIENT_MEDIUM%`  | The 3PID medium, like `email` or `msisdn`                                    | | ||||||
|  | | `%RECIPIENT_ADDRESS%` | The address to which the notification is sent                                | | ||||||
|  |  | ||||||
|  | ## Events | ||||||
|  | ### Room invitation | ||||||
|  | This template is used when someone is invited into a room using an email address which has no known bind to a Matrix ID. | ||||||
|  | #### Placeholders | ||||||
|  | | Placeholder           | Purpose                                                                                  | | ||||||
|  | |-----------------------|------------------------------------------------------------------------------------------| | ||||||
|  | | `%SENDER_ID%`         | Matrix ID of the user who made the invite                                                | | ||||||
|  | | `%SENDER_NAME%`       | Display name of the user who made the invite, if not available/set, empty                | | ||||||
|  | | `%SENDER_NAME_OR_ID%` | Display name of the user who made the invite. If not available/set, its Matrix ID        | | ||||||
|  | | `%INVITE_MEDIUM%`     | The 3PID medium for the invite.                                                          | | ||||||
|  | | `%INVITE_ADDRESS%`    | The 3PID address for the invite.                                                         | | ||||||
|  | | `%ROOM_ID%`           | The Matrix ID of the Room in which the invite took place                                 | | ||||||
|  | | `%ROOM_NAME%`         | The Name of the room in which the invite took place. If not available/set, empty         | | ||||||
|  | | `%ROOM_NAME_OR_ID%`   | The Name of the room in which the invite took place. If not available/set, its Matrix ID | | ||||||
|  |  | ||||||
|  | ### Local validation of 3PID Session | ||||||
|  | This template is used when to user which added their 3PID address to their profile/settings and the session policy | ||||||
|  | allows at least local sessions.   | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | | Placeholder          | Purpose                                                                              | | ||||||
|  | |----------------------|--------------------------------------------------------------------------------------| | ||||||
|  | | `%VALIDATION_LINK%`  | URL, including token, to validate the 3PID session.                                  | | ||||||
|  | | `%VALIDATION_TOKEN%` | The token needed to validate the local session, in case the user cannot use the link | | ||||||
|  |  | ||||||
|  | ### Remote validation of 3PID Session | ||||||
|  | This template is used when to user which added their 3PID address to their profile/settings and the session policy only | ||||||
|  | allows remote sessions. | ||||||
|  |  | ||||||
|  | **NOTE:** 3PID session always require local validation of a token, even if a remote session is enforced.   | ||||||
|  | One cannot bind a Matrix ID to the session until both local and remote sessions have been validated. | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | | Placeholder          | Purpose                                                | | ||||||
|  | |----------------------|--------------------------------------------------------| | ||||||
|  | | `%VALIDATION_TOKEN%` | The token needed to validate the session               | | ||||||
|  | | `%NEXT_URL%`         | URL to continue with remote validation of the session. | | ||||||
							
								
								
									
										86
									
								
								docs/threepids/session/session-views.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								docs/threepids/session/session-views.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | # Web pages for the 3PID sessions | ||||||
|  | You can customize the various pages used during a 3PID validation using [Thymeleaf templates](http://www.thymeleaf.org/). | ||||||
|  |  | ||||||
|  | ## Configuration | ||||||
|  | Pseudo-configuration to illustrate the structure: | ||||||
|  | ```yaml | ||||||
|  | # CONFIGURATION EXAMPLE | ||||||
|  | # DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION | ||||||
|  | view.session.local: | ||||||
|  |   onTokenSubmit: | ||||||
|  |     success: '/path/to/session/local/tokenSubmitSuccess-page.html' | ||||||
|  |     failure: '/path/to/session/local/tokenSubmitFailure-page.html' | ||||||
|  | view.session.localRemote: | ||||||
|  |   onTokenSubmit: | ||||||
|  |     success: '/path/to/session/localRemote/tokenSubmitSuccess-page.html' | ||||||
|  |     failure: '/path/to/session/local/tokenSubmitFailure-page.html' | ||||||
|  | view.session.remote: | ||||||
|  |   onRequest: | ||||||
|  |     success: '/path/to/session/remote/requestSuccess-page.html' | ||||||
|  |     failure: '/path/to/session/remote/requestFailure-page.html' | ||||||
|  |   onCheck: | ||||||
|  |     success: '/path/to/session/remote/checkSuccess-page.html' | ||||||
|  |     failure: '/path/to/session/remote/checkFailure-page.html' | ||||||
|  | # CONFIGURATION EXAMPLE | ||||||
|  | # DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 3PID session are divided into three config sections: | ||||||
|  | - `local` for local-only 3PID sessions | ||||||
|  | - `localRemote` for local 3PID sessions that can also be turned into remote sessions, if the user so desires | ||||||
|  | - `remote` for remote-only 3PID sessions | ||||||
|  |  | ||||||
|  | Each section contains a sub-key per support event. Finally, a `success` and `failure` key is available depending on the | ||||||
|  | outcome of the request. | ||||||
|  |  | ||||||
|  | ## Local | ||||||
|  | ### onTokenSubmit | ||||||
|  | This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the | ||||||
|  | link in a validation email. | ||||||
|  |  | ||||||
|  | The template should typically inform the user that the validation was successful and to go back in their Matrix client | ||||||
|  | to finish the validation process. | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | No object/placeholder are currently available. | ||||||
|  |  | ||||||
|  | ## Local & Remote | ||||||
|  | ### onTokenSubmit | ||||||
|  | This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the | ||||||
|  | link in a validation email. | ||||||
|  |  | ||||||
|  | The template should typically inform the user that their 3PID address will not yet be publicly/globally usable. In case | ||||||
|  | they want to make it, they should start a Remote 3PID session with a given link or that they can go back to their Matrix | ||||||
|  | client if they do not wish to proceed any further. | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | ##### Success | ||||||
|  | `<a th:href="${remoteSessionLink}">text</a>` can be used to display the link to start a Remote 3PID session. | ||||||
|  |  | ||||||
|  | ##### Failure | ||||||
|  | No object/placeholder are currently available. | ||||||
|  |  | ||||||
|  | ## Remote | ||||||
|  | ### onRequest | ||||||
|  | This is triggered when a user starts a Remote 3PID session, usually from a link produced in the `local.onTokenSubmit` | ||||||
|  | view or in a remote-only 3PID notification. | ||||||
|  |  | ||||||
|  | The template should typically inform the user that the remote creation was successful, followed the instructions sent by | ||||||
|  | the remote Identity server and, once that is done, click a link to validate the session. | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | ##### Success | ||||||
|  | `<a th:href="${checkLink}">text</a>` can be used to display the link to validate the Remote 3PID session. | ||||||
|  |  | ||||||
|  | ##### Failure | ||||||
|  | No object/placeholder are currently available. | ||||||
|  |  | ||||||
|  | ### onCheck | ||||||
|  | This is triggered when a user attempts to inform the Identity server that the Remote 3PID session has been validated | ||||||
|  | with the remote Identity server. | ||||||
|  |  | ||||||
|  | The template should typically inform the user that the validation was successful and to go back in their Matrix client | ||||||
|  | to finish the validation process. | ||||||
|  |  | ||||||
|  | #### Placeholders | ||||||
|  | No object/placeholder are currently available. | ||||||
							
								
								
									
										262
									
								
								docs/threepids/session/session.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								docs/threepids/session/session.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | |||||||
|  | # 3PID Sessions | ||||||
|  | - [Overview](#overview) | ||||||
|  | - [Purpose](#purpose) | ||||||
|  | - [Federation](#federation) | ||||||
|  |   - [3PID scope](#3pid-scope) | ||||||
|  |   - [Session scope](#session-scope) | ||||||
|  | - [Notifications](#notifications) | ||||||
|  |   - [Email](#email) | ||||||
|  |   - [Phone numbers](#msisdn-(phone-numbers)) | ||||||
|  | - [Usage](#usage) | ||||||
|  |   - [Configuration](#configuration) | ||||||
|  |   - [Web views](#web-views) | ||||||
|  |   - [Scenarios](#scenarios) | ||||||
|  |     - [Default](#default) | ||||||
|  |     - [Local sessions only](#local-sessions-only) | ||||||
|  |     - [Remote sessions only](#remote-sessions-only) | ||||||
|  |     - [Sessions disabled](#sessions-disabled) | ||||||
|  |  | ||||||
|  | ## 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. | ||||||
|  |  | ||||||
|  | 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 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.   | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | 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 | ||||||
|  | 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: | ||||||
|  | - 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 | ||||||
|  | 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. | ||||||
|  |  | ||||||
|  | Email addresses can either be local or remote 3PID, depending on the domain. If the address is one from the configured | ||||||
|  | domain in the Identity server, it will be scoped as local. If it is from another domain, it will be as remote. | ||||||
|  |  | ||||||
|  | Phone number can only be scoped as remote, since there is currently no way to perform DNS queries that would lead back | ||||||
|  | to the Identity server who validated the phone number. | ||||||
|  |  | ||||||
|  | ### Session scope | ||||||
|  | Sessions can be scoped as: | ||||||
|  | - Local only - validate 3PIDs directly, do not allow the creation of 3PID sessions on a remote Identity server. | ||||||
|  | - Local and Remote - validate 3PIDs directly, offer users to option to also validate and bind 3PID on another server. | ||||||
|  | - Remote only - validate and bind 3PIDs on another server, no validation or bind done locally. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | **IMPORTANT NOTE:** mxisd does not store bindings directly. While a user can see its email, phone number or any other | ||||||
|  | 3PID in its settings/profile, it does **NOT** mean it is published anywhere and can be used to invite/search the user. | ||||||
|  | Identity stores are the ones holding such data.   | ||||||
|  | If you still want added arbitrary 3PIDs to be discoverable on a synapse Homeserver, use the corresponding [Identity store](../../stores/synapse.md). | ||||||
|  |  | ||||||
|  | See the [Scenarios](#scenarios) for more info on how and why. | ||||||
|  |  | ||||||
|  | ## Notifications | ||||||
|  | 3PIDs are validated by sending a pre-formatted message containing a token to that 3PID address, which must be given to the | ||||||
|  | Identity server that received the request. This is usually done by means of a URL to visit for email or a short number | ||||||
|  | received by SMS for phone numbers. | ||||||
|  |  | ||||||
|  | mxisd use two components for this: | ||||||
|  | - Generator which produces the message to be sent with the necessary information the user needs to validate their session. | ||||||
|  | - Connector which actually send the notification (e.g. SMTP for email). | ||||||
|  |  | ||||||
|  | Built-in generators and connectors for supported 3PID types: | ||||||
|  |  | ||||||
|  | ### Email | ||||||
|  | Generators: | ||||||
|  | - [Template](../notification/template-generator.md) | ||||||
|  |  | ||||||
|  | Connectors: | ||||||
|  | - [SMTP](../medium/email/smtp-connector.md) | ||||||
|  |  | ||||||
|  | #### MSISDN (Phone numbers) | ||||||
|  | Generators: | ||||||
|  | - [Template](../notification/template-generator.md) | ||||||
|  |  | ||||||
|  | Connectors: | ||||||
|  |  - [Twilio](../medium/msisdn/twilio-connector.md) with SMS | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  | ### Configuration | ||||||
|  | The following example of configuration (incomplete extract) shows which items are relevant for 3PID sessions. | ||||||
|  |  | ||||||
|  | **IMPORTANT:** Most configuration items shown have default values and should not be included in your own configuration | ||||||
|  | file unless you want to specifically overwrite them. | ||||||
|  | ```yaml | ||||||
|  | # CONFIGURATION EXAMPLE | ||||||
|  | # DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION | ||||||
|  | session.policy.validation.enabled: true | ||||||
|  | session.policy.validation.forLocal: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: true | ||||||
|  |   toRemote: | ||||||
|  |     enabled: true | ||||||
|  |     server: 'configExample'  # Not to be included in config! Already present in default config! | ||||||
|  | session.policy.validation.forRemote: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: true | ||||||
|  |   toRemote: | ||||||
|  |     enabled: true | ||||||
|  |     server: 'configExample'  # Not to be included in config! Already present in default config! | ||||||
|  | # DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION | ||||||
|  | # CONFIGURATION EXAMPLE | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | `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`   | ||||||
|  | It is also divided into two sections: `forLocal` and `forRemote` which refers to the 3PID scopes.   | ||||||
|  |  | ||||||
|  | Each scope is divided into three parts: | ||||||
|  | - global on/off switch for 3PID sessions using `.enabled` | ||||||
|  | - `toLocal` allowing or not local 3PID session validations | ||||||
|  | - `toRemote` allowing or not remote 3PID session validations and to which server such sessions should be sent.    | ||||||
|  |   `.server` takes a Matrix Identity server list label. Only the first server in the list is currently used. | ||||||
|  |  | ||||||
|  | If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID | ||||||
|  | locally validated. | ||||||
|  |  | ||||||
|  | ### Web views | ||||||
|  | Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.   | ||||||
|  | If the session or token is invalid, an error page is displayed.   | ||||||
|  | Workflow pages are also available for the remote 3PID session process. | ||||||
|  |  | ||||||
|  | See [the dedicated document](session-views.md) | ||||||
|  | on how to configure/customize/brand those pages to your liking. | ||||||
|  |  | ||||||
|  | ### Scenarios | ||||||
|  | It is important to keep in mind that mxisd does not create bindings, irrelevant if a user added a 3PID to their profile.   | ||||||
|  | Instead, when queried for bindings, mxisd will query Identity stores which are responsible to store this kind of information. | ||||||
|  |  | ||||||
|  | This has the side effect that any 3PID added to a user profile which is NOT within a configured and enabled Identity backend | ||||||
|  | will simply not be usable for search or invites, **even on the same Homeserver!**   | ||||||
|  | mxisd does not store binds on purpose, as one of its primary goal is to ensure maximum compatibility with federation | ||||||
|  | and the rest of the Matrix ecosystem is preserved. | ||||||
|  |  | ||||||
|  | Nonetheless, because mxisd also aims at offering support for tight control over identity data, it is possible to have | ||||||
|  | such 3PID bindings available for search and invite queries on synapse with the corresponding [Identity store](../../stores/synapse.md). | ||||||
|  |  | ||||||
|  | See the [Local sessions only](#local-sessions-only) use case for more information on how to configure. | ||||||
|  |  | ||||||
|  | #### Default | ||||||
|  | By default, mxisd allows the following: | ||||||
|  |  | ||||||
|  | |                 | Local Session     | Remote Session | | ||||||
|  | |-----------------|-------------------|----------------| | ||||||
|  | | **Local 3PID**  | Yes               | Yes, offered   | | ||||||
|  | | **Remote 3PID** | No, Remote forced | Yes            | | ||||||
|  |  | ||||||
|  | This is usually what people expect and will feel natural to users and does not involve further integration. | ||||||
|  |  | ||||||
|  | This allows to stay in control for e-mail addresses which domain matches your Matrix environment, still making them | ||||||
|  | discoverable with federation but not recorded in a 3rd party Identity server which is not under your control.   | ||||||
|  | Users still get the possibility to publish globally their address if needed. | ||||||
|  |  | ||||||
|  | Other e-mail addresses and phone number will be redirected to remote sessions to ensure full compatibility with the Matrix | ||||||
|  | ecosystem and other federated servers. | ||||||
|  |  | ||||||
|  | #### Local sessions only | ||||||
|  | **NOTE:** This does not affect 3PID lookups (queries to find Matrix IDs). See [Federation](../../features/federation.md) | ||||||
|  | to disable remote lookup for those. | ||||||
|  |  | ||||||
|  | This configuration ensures maximum confidentiality and privacy. | ||||||
|  | Typical use cases: | ||||||
|  | - Private Homeserver, not federated | ||||||
|  | - Internal Homeserver without direct Internet access | ||||||
|  | - Custom product based on Matrix which does not federate | ||||||
|  |  | ||||||
|  | No 3PID will be sent to a remote Identity server and all validation will be performed locally.   | ||||||
|  | On the flip side, people with *Remote* 3PID scopes will not be found from other servers. | ||||||
|  |  | ||||||
|  | Use the following values: | ||||||
|  | ```yaml | ||||||
|  | session.policy.validation.enabled: true | ||||||
|  | session.policy.validation.forLocal: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: true | ||||||
|  |   toRemote: | ||||||
|  |     enabled: false | ||||||
|  | session.policy.validation.forRemote: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: true | ||||||
|  |   toRemote: | ||||||
|  |     enabled: false | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **IMPORTANT**: When using local-only mode and if you are using synapse, you will also need to enable its dedicated Identity | ||||||
|  | store if you want user searches and invites to work. To do so, see the [dedicated document](../../stores/synapse.md). | ||||||
|  |  | ||||||
|  | #### Remote sessions only | ||||||
|  | This configuration ensures all 3PID are made public for maximum compatibility and reach within the Matrix ecosystem, at | ||||||
|  | the cost of confidentiality and privacy.   | ||||||
|  |  | ||||||
|  | Typical use cases: | ||||||
|  | - Public Homeserver | ||||||
|  | - Homeserver with registration enabled | ||||||
|  |  | ||||||
|  | Use the following values: | ||||||
|  | ```yaml | ||||||
|  | session.policy.validation.enabled: true | ||||||
|  | session.policy.validation.forLocal: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: false | ||||||
|  |   toRemote: | ||||||
|  |     enabled: true | ||||||
|  | session.policy.validation.forRemote: | ||||||
|  |   enabled: true | ||||||
|  |   toLocal: false | ||||||
|  |   toRemote: | ||||||
|  |     enabled: true | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Sessions disabled | ||||||
|  | This configuration would disable 3PID session altogether, preventing users from adding emails and/or phone numbers to | ||||||
|  | their profiles.   | ||||||
|  | This would be used if mxisd is also performing authentication for the Homeserver, typically with synapse and the | ||||||
|  | [REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth). | ||||||
|  |  | ||||||
|  | **This mode comes with several important restrictions:** | ||||||
|  | - This does not prevent users from removing 3PID from their profile. They would be unable to add them back! | ||||||
|  | - This prevents users from initiating remote session to make their 3PID binds globally visible | ||||||
|  |  | ||||||
|  | It is therefore recommended to not fully disable sessions but instead restrict specific set of 3PID and Session scopes. | ||||||
|  |  | ||||||
|  | Use the following values to enable this mode: | ||||||
|  | ```yaml | ||||||
|  | session.policy.validation.enabled: false | ||||||
|  | ``` | ||||||
							
								
								
									
										
											BIN
										
									
								
								media/mx-id-icon-reverse.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								media/mx-id-icon-reverse.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								media/mx-id-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								media/mx-id-icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.7 KiB | 
| @@ -1,6 +1,6 @@ | |||||||
| Package: mxisd | Package: mxisd | ||||||
| Maintainer: Kamax.io <foss@kamax.io> | Maintainer: Kamax.io <foss@kamax.io> | ||||||
| Homepage: https://github.com/kamax-io/mxisd | Homepage: https://github.com/kamax-matrix/mxisd | ||||||
| Description: Federated Matrix Identity Server | Description: Federated Matrix Identity Server | ||||||
| Architecture: all | Architecture: all | ||||||
| Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless | Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless | ||||||
|   | |||||||
| @@ -1,2 +1,25 @@ | |||||||
| #!/bin/sh | #!/usr/bin/env bash | ||||||
|  | if [[ -n "$CONF_FILE_PATH" ]] && [ ! -f "$CONF_FILE_PATH" ]; then | ||||||
|  |     echo "Generating config file $CONF_FILE_PATH" | ||||||
|  |     touch "CONF_FILE_PATH" | ||||||
|  |  | ||||||
|  |     if [[ -n "$MATRIX_DOMAIN" ]]; then | ||||||
|  |         echo "Setting matrix domain to $MATRIX_DOMAIN" | ||||||
|  |         echo "matrix.domain: $MATRIX_DOMAIN" >> "$CONF_FILE_PATH" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     if [[ -n "$SIGN_KEY_PATH" ]]; then | ||||||
|  |         echo "Setting signing key path to $SIGN_KEY_PATH" | ||||||
|  |         echo "key.path: $SIGN_KEY_PATH" >> "$CONF_FILE_PATH" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     if [[ -n "$SQLITE_DATABASE_PATH" ]]; then | ||||||
|  |         echo "Setting SQLite DB path to $SQLITE_DATABASE_PATH" | ||||||
|  |         echo "storage.provider.sqlite.database: $SQLITE_DATABASE_PATH" >> "$CONF_FILE_PATH" | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     echo "Starting mxisd..." | ||||||
|  |     echo | ||||||
|  | fi | ||||||
|  |  | ||||||
| exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar | exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar | ||||||
| @@ -1,156 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lang.StringUtils |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.InitializingBean |  | ||||||
| import org.springframework.boot.context.properties.ConfigurationProperties |  | ||||||
| import org.springframework.context.annotation.Configuration |  | ||||||
|  |  | ||||||
| @Configuration |  | ||||||
| @ConfigurationProperties(prefix = "ldap") |  | ||||||
| class LdapConfig implements InitializingBean { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(LdapConfig.class) |  | ||||||
|  |  | ||||||
|     private boolean enabled |  | ||||||
|     private boolean tls |  | ||||||
|     private String host |  | ||||||
|     private int port |  | ||||||
|     private String baseDn |  | ||||||
|     private String type |  | ||||||
|     private String attribute |  | ||||||
|     private String bindDn |  | ||||||
|     private String bindPassword |  | ||||||
|     private Map<String, String> mappings |  | ||||||
|  |  | ||||||
|     boolean getEnabled() { |  | ||||||
|         return enabled |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setEnabled(boolean enabled) { |  | ||||||
|         this.enabled = enabled |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     boolean getTls() { |  | ||||||
|         return tls |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setTls(boolean tls) { |  | ||||||
|         this.tls = tls |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getHost() { |  | ||||||
|         return host |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setHost(String host) { |  | ||||||
|         this.host = host |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int getPort() { |  | ||||||
|         return port |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setPort(int port) { |  | ||||||
|         this.port = port |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getBaseDn() { |  | ||||||
|         return baseDn |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setBaseDn(String baseDn) { |  | ||||||
|         this.baseDn = baseDn |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getType() { |  | ||||||
|         return type |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setType(String type) { |  | ||||||
|         this.type = type |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getAttribute() { |  | ||||||
|         return attribute |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setAttribute(String attribute) { |  | ||||||
|         this.attribute = attribute |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getBindDn() { |  | ||||||
|         return bindDn |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setBindDn(String bindDn) { |  | ||||||
|         this.bindDn = bindDn |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getBindPassword() { |  | ||||||
|         return bindPassword |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setBindPassword(String bindPassword) { |  | ||||||
|         this.bindPassword = bindPassword |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Map<String, String> getMappings() { |  | ||||||
|         return mappings |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setMappings(Map<String, String> mappings) { |  | ||||||
|         this.mappings = mappings |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Optional<String> getMapping(String type) { |  | ||||||
|         if (mappings == null) { |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.ofNullable(mappings.get(type)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     void afterPropertiesSet() throws Exception { |  | ||||||
|         log.info("LDAP enabled: {}", getEnabled()) |  | ||||||
|  |  | ||||||
|         if (!getEnabled()) { |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Matrix ID type: {}", getType()) |  | ||||||
|         log.info("LDAP Host: {}", getHost()) |  | ||||||
|         log.info("LDAP Bind DN: {}", getBindDn()) |  | ||||||
|         log.info("LDAP Attribute: {}", getAttribute()) |  | ||||||
|  |  | ||||||
|         if (StringUtils.isBlank(getHost())) { |  | ||||||
|             throw new IllegalStateException("LDAP Host must be configured!") |  | ||||||
|         } |  | ||||||
|         if (StringUtils.isBlank(getAttribute())) { |  | ||||||
|             throw new IllegalStateException("LDAP attribute must be configured!") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| package io.kamax.mxisd.config |  | ||||||
|  |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.InitializingBean |  | ||||||
| import org.springframework.boot.context.properties.ConfigurationProperties |  | ||||||
| import org.springframework.context.annotation.Configuration |  | ||||||
|  |  | ||||||
| @Configuration |  | ||||||
| @ConfigurationProperties(prefix = "lookup.recursive.bridge") |  | ||||||
| class RecursiveLookupBridgeConfig implements InitializingBean { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class) |  | ||||||
|  |  | ||||||
|     private boolean enabled |  | ||||||
|     private boolean recursiveOnly |  | ||||||
|     private String server |  | ||||||
|     private Map<String, String> mappings |  | ||||||
|  |  | ||||||
|     boolean getEnabled() { |  | ||||||
|         return enabled |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setEnabled(boolean enabled) { |  | ||||||
|         this.enabled = enabled |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     boolean getRecursiveOnly() { |  | ||||||
|         return recursiveOnly |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setRecursiveOnly(boolean recursiveOnly) { |  | ||||||
|         this.recursiveOnly = recursiveOnly |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getServer() { |  | ||||||
|         return server |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setServer(String server) { |  | ||||||
|         this.server = server |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Map<String, String> getMappings() { |  | ||||||
|         return mappings |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void setMappings(Map<String, String> mappings) { |  | ||||||
|         this.mappings = mappings |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     void afterPropertiesSet() throws Exception { |  | ||||||
|         log.info("Enabled: {}", getEnabled()) |  | ||||||
|         log.info("Recursive only: {}", getRecursiveOnly()) |  | ||||||
|         log.info("Server: {}", getServer()) |  | ||||||
|         log.info("Mappings: {}", mappings.size()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,71 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.controller.v1 |  | ||||||
|  |  | ||||||
| import groovy.json.JsonOutput |  | ||||||
| import io.kamax.mxisd.exception.BadRequestException |  | ||||||
| import io.kamax.mxisd.exception.NotImplementedException |  | ||||||
| import io.kamax.mxisd.key.KeyManager |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.web.bind.annotation.PathVariable |  | ||||||
| import org.springframework.web.bind.annotation.RequestMapping |  | ||||||
| import org.springframework.web.bind.annotation.RestController |  | ||||||
|  |  | ||||||
| import javax.servlet.http.HttpServletRequest |  | ||||||
|  |  | ||||||
| import static org.springframework.web.bind.annotation.RequestMethod.GET |  | ||||||
|  |  | ||||||
| @RestController |  | ||||||
| class KeyController { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(KeyController.class) |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private KeyManager keyMgr |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET) |  | ||||||
|     String getKey(@PathVariable String keyType, @PathVariable int keyId) { |  | ||||||
|         if (!"ed25519".contentEquals(keyType)) { |  | ||||||
|             throw new BadRequestException("Invalid algorithm: " + keyType) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return JsonOutput.toJson([ |  | ||||||
|                 public_key: keyMgr.getPublicKeyBase64(keyId) |  | ||||||
|         ]) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET) |  | ||||||
|     String checkEphemeralKeyValidity(HttpServletRequest request) { |  | ||||||
|         log.error("{} was requested but not implemented", request.getRequestURL()) |  | ||||||
|  |  | ||||||
|         throw new NotImplementedException() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET) |  | ||||||
|     String checkKeyValidity(HttpServletRequest request) { |  | ||||||
|         log.error("{} was requested but not implemented", request.getRequestURL()) |  | ||||||
|  |  | ||||||
|         throw new NotImplementedException() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,103 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.controller.v1 |  | ||||||
|  |  | ||||||
| import groovy.json.JsonOutput |  | ||||||
| import groovy.json.JsonSlurper |  | ||||||
| import io.kamax.mxisd.lookup.BulkLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.strategy.LookupStrategy |  | ||||||
| import io.kamax.mxisd.signature.SignatureManager |  | ||||||
| import org.apache.commons.lang.StringUtils |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.web.bind.annotation.RequestMapping |  | ||||||
| import org.springframework.web.bind.annotation.RequestParam |  | ||||||
| import org.springframework.web.bind.annotation.RestController |  | ||||||
|  |  | ||||||
| import javax.servlet.http.HttpServletRequest |  | ||||||
|  |  | ||||||
| import static org.springframework.web.bind.annotation.RequestMethod.GET |  | ||||||
| import static org.springframework.web.bind.annotation.RequestMethod.POST |  | ||||||
|  |  | ||||||
| @RestController |  | ||||||
| class MappingController { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(MappingController.class) |  | ||||||
|     private JsonSlurper json = new JsonSlurper() |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private LookupStrategy strategy |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private SignatureManager signMgr |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET) |  | ||||||
|     String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) { |  | ||||||
|         String remote = StringUtils.defaultIfBlank(request.getHeader("X-FORWARDED-FOR"), request.getRemoteAddr()) |  | ||||||
|         log.info("Got request from {}", remote) |  | ||||||
|  |  | ||||||
|         SingleLookupRequest lookupRequest = new SingleLookupRequest() |  | ||||||
|         lookupRequest.setRequester(remote) |  | ||||||
|         lookupRequest.setType(medium) |  | ||||||
|         lookupRequest.setThreePid(address) |  | ||||||
|  |  | ||||||
|         Optional<?> lookupOpt = strategy.find(lookupRequest) |  | ||||||
|         if (!lookupOpt.isPresent()) { |  | ||||||
|             log.info("No mapping was found, return empty JSON object") |  | ||||||
|             return JsonOutput.toJson([]) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         def lookup = lookupOpt.get() |  | ||||||
|         if (lookup['signatures'] == null) { |  | ||||||
|             log.info("lookup is not signed yet, we sign it") |  | ||||||
|             lookup['signatures'] = signMgr.signMessage(JsonOutput.toJson(lookup)) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return JsonOutput.toJson(lookup) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/bulk_lookup", method = POST) |  | ||||||
|     String bulkLookup(HttpServletRequest request) { |  | ||||||
|         String remote = StringUtils.defaultIfBlank(request.getHeader("X-FORWARDED-FOR"), request.getRemoteAddr()) |  | ||||||
|         log.info("Got request from {}", remote) |  | ||||||
|  |  | ||||||
|         BulkLookupRequest lookupRequest = new BulkLookupRequest() |  | ||||||
|         lookupRequest.setRequester(remote) |  | ||||||
|  |  | ||||||
|         ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText()) |  | ||||||
|         List<ThreePidMapping> mappings = new ArrayList<>() |  | ||||||
|         for (List<String> mappingRaw : input.getThreepids()) { |  | ||||||
|             ThreePidMapping mapping = new ThreePidMapping() |  | ||||||
|             mapping.setMedium(mappingRaw.get(0)) |  | ||||||
|             mapping.setValue(mappingRaw.get(1)) |  | ||||||
|             mappings.add(mapping) |  | ||||||
|         } |  | ||||||
|         lookupRequest.setMappings(mappings) |  | ||||||
|  |  | ||||||
|         ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer() |  | ||||||
|         answer.addAll(strategy.find(lookupRequest)) |  | ||||||
|         return JsonOutput.toJson(answer) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,154 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.controller.v1 |  | ||||||
|  |  | ||||||
| import com.google.gson.Gson |  | ||||||
| import com.google.gson.JsonObject |  | ||||||
| import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson |  | ||||||
| import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson |  | ||||||
| import io.kamax.mxisd.exception.BadRequestException |  | ||||||
| import io.kamax.mxisd.lookup.ThreePid |  | ||||||
| import io.kamax.mxisd.mapping.MappingManager |  | ||||||
| import org.apache.commons.io.IOUtils |  | ||||||
| import org.apache.commons.lang.StringUtils |  | ||||||
| import org.apache.http.HttpStatus |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.web.bind.annotation.PathVariable |  | ||||||
| import org.springframework.web.bind.annotation.RequestMapping |  | ||||||
| import org.springframework.web.bind.annotation.RequestParam |  | ||||||
| import org.springframework.web.bind.annotation.RestController |  | ||||||
|  |  | ||||||
| import javax.servlet.http.HttpServletRequest |  | ||||||
| import javax.servlet.http.HttpServletResponse |  | ||||||
| import java.nio.charset.StandardCharsets |  | ||||||
|  |  | ||||||
| @RestController |  | ||||||
| class SessionController { |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private MappingManager mgr |  | ||||||
|  |  | ||||||
|     private Gson gson = new Gson() |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(SessionController.class) |  | ||||||
|  |  | ||||||
|     private <T> T fromJson(HttpServletRequest req, Class<T> obj) { |  | ||||||
|         gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/requestToken") |  | ||||||
|     String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) { |  | ||||||
|         log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) |  | ||||||
|  |  | ||||||
|         if (StringUtils.equals("email", medium)) { |  | ||||||
|             SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class) |  | ||||||
|             return gson.toJson(new Sid(mgr.create(req))) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (StringUtils.equals("msisdn", medium)) { |  | ||||||
|             SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class) |  | ||||||
|             return gson.toJson(new Sid(mgr.create(req))) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         JsonObject obj = new JsonObject(); |  | ||||||
|         obj.addProperty("errcode", "M_INVALID_3PID_TYPE") |  | ||||||
|         obj.addProperty("error", medium + " is not supported as a 3PID type") |  | ||||||
|         response.setStatus(HttpStatus.SC_BAD_REQUEST) |  | ||||||
|         return gson.toJson(obj) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/submitToken") |  | ||||||
|     String validate(HttpServletRequest request, |  | ||||||
|                     @RequestParam String sid, |  | ||||||
|                     @RequestParam("client_secret") String secret, @RequestParam String token) { |  | ||||||
|         log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) |  | ||||||
|  |  | ||||||
|         mgr.validate(sid, secret, token) |  | ||||||
|  |  | ||||||
|         return "{}" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid") |  | ||||||
|     String check(HttpServletRequest request, HttpServletResponse response, |  | ||||||
|                  @RequestParam String sid, @RequestParam("client_secret") String secret) { |  | ||||||
|         log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) |  | ||||||
|  |  | ||||||
|         Optional<ThreePid> result = mgr.getValidated(sid, secret) |  | ||||||
|         if (result.isPresent()) { |  | ||||||
|             log.info("requested session was validated") |  | ||||||
|             ThreePid pid = result.get() |  | ||||||
|  |  | ||||||
|             JsonObject obj = new JsonObject() |  | ||||||
|             obj.addProperty("medium", pid.getMedium()) |  | ||||||
|             obj.addProperty("address", pid.getAddress()) |  | ||||||
|             obj.addProperty("validated_at", pid.getValidation().toEpochMilli()) |  | ||||||
|  |  | ||||||
|             return gson.toJson(obj); |  | ||||||
|         } else { |  | ||||||
|             log.info("requested session was not validated") |  | ||||||
|  |  | ||||||
|             JsonObject obj = new JsonObject() |  | ||||||
|             obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED") |  | ||||||
|             obj.addProperty("error", "sid, secret or session not valid") |  | ||||||
|             response.setStatus(HttpStatus.SC_BAD_REQUEST) |  | ||||||
|             return gson.toJson(obj) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind") |  | ||||||
|     String bind(HttpServletRequest request, HttpServletResponse response, |  | ||||||
|                 @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) { |  | ||||||
|         String data = IOUtils.toString(request.getReader()) |  | ||||||
|         log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) |  | ||||||
|         try { |  | ||||||
|             mgr.bind(sid, secret, mxid) |  | ||||||
|             return "{}" |  | ||||||
|         } catch (BadRequestException e) { |  | ||||||
|             log.info("requested session was not validated") |  | ||||||
|  |  | ||||||
|             JsonObject obj = new JsonObject() |  | ||||||
|             obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED") |  | ||||||
|             obj.addProperty("error", e.getMessage()) |  | ||||||
|             response.setStatus(HttpStatus.SC_BAD_REQUEST) |  | ||||||
|             return gson.toJson(obj) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class Sid { |  | ||||||
|  |  | ||||||
|         private String sid; |  | ||||||
|  |  | ||||||
|         public Sid(String sid) { |  | ||||||
|             setSid(sid); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         String getSid() { |  | ||||||
|             return sid |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         void setSid(String sid) { |  | ||||||
|             this.sid = sid |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| package io.kamax.mxisd.controller.v1.io; |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.mapping.MappingSession; |  | ||||||
|  |  | ||||||
| public abstract class GenericTokenRequestJson implements MappingSession { |  | ||||||
|  |  | ||||||
|     private String client_secret; |  | ||||||
|     private int send_attempt; |  | ||||||
|     private String id_server; |  | ||||||
|  |  | ||||||
|     public String getSecret() { |  | ||||||
|         return client_secret; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getAttempt() { |  | ||||||
|         return send_attempt; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getServer() { |  | ||||||
|         return id_server; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| package io.kamax.mxisd.controller.v1.io; |  | ||||||
|  |  | ||||||
| public class SessionEmailTokenRequestJson extends GenericTokenRequestJson { |  | ||||||
|  |  | ||||||
|     private String email; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getMedium() { |  | ||||||
|         return "email"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getValue() { |  | ||||||
|         return email; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package io.kamax.mxisd.controller.v1.io; |  | ||||||
|  |  | ||||||
| import com.google.i18n.phonenumbers.NumberParseException; |  | ||||||
| import com.google.i18n.phonenumbers.PhoneNumberUtil; |  | ||||||
| import com.google.i18n.phonenumbers.Phonenumber; |  | ||||||
|  |  | ||||||
| public class SessionPhoneTokenRequestJson extends GenericTokenRequestJson { |  | ||||||
|  |  | ||||||
|     private static PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); |  | ||||||
|  |  | ||||||
|     private String country; |  | ||||||
|     private String phone_number; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getMedium() { |  | ||||||
|         return "msisdn"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getValue() { |  | ||||||
|         try { |  | ||||||
|             Phonenumber.PhoneNumber num = phoneUtil.parse(phone_number, country); |  | ||||||
|             return phoneUtil.format(num, PhoneNumberUtil.PhoneNumberFormat.E164).replace("+", ""); |  | ||||||
|         } catch (NumberParseException e) { |  | ||||||
|             throw new IllegalArgumentException("Invalid phone number"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,106 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.key |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.KeyConfig |  | ||||||
| import net.i2p.crypto.eddsa.EdDSAEngine |  | ||||||
| import net.i2p.crypto.eddsa.EdDSAPrivateKey |  | ||||||
| import net.i2p.crypto.eddsa.EdDSAPublicKey |  | ||||||
| import net.i2p.crypto.eddsa.KeyPairGenerator |  | ||||||
| import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable |  | ||||||
| import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec |  | ||||||
| import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec |  | ||||||
| import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec |  | ||||||
| import org.apache.commons.io.FileUtils |  | ||||||
| import org.springframework.beans.factory.InitializingBean |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| import java.nio.charset.StandardCharsets |  | ||||||
| import java.nio.file.Files |  | ||||||
| import java.nio.file.Path |  | ||||||
| import java.nio.file.Paths |  | ||||||
| import java.security.KeyPair |  | ||||||
| import java.security.MessageDigest |  | ||||||
| import java.security.PrivateKey |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class KeyManager implements InitializingBean { |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private KeyConfig keyCfg |  | ||||||
|  |  | ||||||
|     private EdDSAParameterSpec keySpecs |  | ||||||
|     private EdDSAEngine signEngine |  | ||||||
|     private List<KeyPair> keys |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     void afterPropertiesSet() throws Exception { |  | ||||||
|         keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) |  | ||||||
|         signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm())) |  | ||||||
|         keys = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         Path privKey = Paths.get(keyCfg.getPath()) |  | ||||||
|  |  | ||||||
|         if (!Files.exists(privKey)) { |  | ||||||
|             KeyPair pair = (new KeyPairGenerator()).generateKeyPair() |  | ||||||
|             String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()) |  | ||||||
|             FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1) |  | ||||||
|             keys.add(pair) |  | ||||||
|         } else { |  | ||||||
|             if (Files.isDirectory(privKey)) { |  | ||||||
|                 throw new RuntimeException("Invalid path for private key: ${privKey.toString()}") |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (Files.isReadable(privKey)) { |  | ||||||
|                 byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1)) |  | ||||||
|                 EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs) |  | ||||||
|                 EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs) |  | ||||||
|                 keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec))) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     int getCurrentIndex() { |  | ||||||
|         return 0 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     KeyPair getKeys(int index) { |  | ||||||
|         return keys.get(index) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     PrivateKey getPrivateKey(int index) { |  | ||||||
|         return getKeys(index).getPrivate() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     EdDSAPublicKey getPublicKey(int index) { |  | ||||||
|         return (EdDSAPublicKey) getKeys(index).getPublic() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     EdDSAParameterSpec getSpecs() { |  | ||||||
|         return keySpecs |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getPublicKeyBase64(int index) { |  | ||||||
|         return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| package io.kamax.mxisd.lookup; |  | ||||||
|  |  | ||||||
| import java.time.Instant; |  | ||||||
|  |  | ||||||
| public class ThreePid { |  | ||||||
|  |  | ||||||
|     private String medium; |  | ||||||
|     private String address; |  | ||||||
|     private Instant validation; |  | ||||||
|  |  | ||||||
|     public ThreePid(String medium, String address, Instant validation) { |  | ||||||
|         this.medium = medium; |  | ||||||
|         this.address = address; |  | ||||||
|         this.validation = validation; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getMedium() { |  | ||||||
|         return medium; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getAddress() { |  | ||||||
|         return address; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Instant getValidation() { |  | ||||||
|         return validation; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,230 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.provider |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.ServerConfig |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher |  | ||||||
| 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 org.xbill.DNS.Lookup |  | ||||||
| import org.xbill.DNS.SRVRecord |  | ||||||
| import org.xbill.DNS.Type |  | ||||||
|  |  | ||||||
| import java.util.concurrent.ForkJoinPool |  | ||||||
| import java.util.concurrent.RecursiveTask |  | ||||||
| import java.util.function.Function |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class DnsLookupProvider implements IThreePidProvider { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class) |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private ServerConfig srvCfg |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private IRemoteIdentityServerFetcher fetcher |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isEnabled() { |  | ||||||
|         return true |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isLocal() { |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     int getPriority() { |  | ||||||
|         return 10 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String getSrvRecordName(String domain) { |  | ||||||
|         return "_matrix-identity._tcp." + domain |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Optional<String> getDomain(String email) { |  | ||||||
|         int atIndex = email.lastIndexOf("@") |  | ||||||
|         if (atIndex == -1) { |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.of(email.substring(atIndex + 1)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // TODO use caching mechanism |  | ||||||
|     Optional<String> findIdentityServerForDomain(String domain) { |  | ||||||
|         if (StringUtils.equals(srvCfg.getName(), domain)) { |  | ||||||
|             log.info("We are authoritative for {}, no remote lookup", domain) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Performing SRV lookup") |  | ||||||
|         String lookupDns = getSrvRecordName(domain) |  | ||||||
|         log.info("Lookup name: {}", lookupDns) |  | ||||||
|  |  | ||||||
|         SRVRecord[] records = (SRVRecord[]) new Lookup(lookupDns, Type.SRV).run() |  | ||||||
|         if (records != null) { |  | ||||||
|             Arrays.sort(records, new Comparator<SRVRecord>() { |  | ||||||
|  |  | ||||||
|                 @Override |  | ||||||
|                 int compare(SRVRecord o1, SRVRecord o2) { |  | ||||||
|                     return Integer.compare(o1.getPriority(), o2.getPriority()) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|             }) |  | ||||||
|  |  | ||||||
|             for (SRVRecord record : records) { |  | ||||||
|                 log.info("Found SRV record: {}", record.toString()) |  | ||||||
|                 String baseUrl = "https://${record.getTarget().toString(true)}:${record.getPort()}" |  | ||||||
|                 if (fetcher.isUsable(baseUrl)) { |  | ||||||
|                     log.info("Found Identity Server for domain {} at {}", domain, baseUrl) |  | ||||||
|                     return Optional.of(baseUrl) |  | ||||||
|                 } else { |  | ||||||
|                     log.info("{} is not a usable Identity Server", baseUrl) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             log.info("No SRV record for {}", lookupDns) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Performing basic lookup using domain name {}", domain) |  | ||||||
|         String baseUrl = "https://" + domain |  | ||||||
|         if (fetcher.isUsable(baseUrl)) { |  | ||||||
|             log.info("Found Identity Server for domain {} at {}", domain, baseUrl) |  | ||||||
|             return Optional.of(baseUrl) |  | ||||||
|         } else { |  | ||||||
|             log.info("{} is not a usable Identity Server", baseUrl) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     Optional<?> find(SingleLookupRequest request) { |  | ||||||
|         if (!StringUtils.equals("email", request.getType())) { // TODO use enum |  | ||||||
|             log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid()) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Performing DNS lookup for {}", request.getThreePid()) |  | ||||||
|  |  | ||||||
|         String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1) |  | ||||||
|         log.info("Domain name for {}: {}", request.getThreePid(), domain) |  | ||||||
|         Optional<String> baseUrl = findIdentityServerForDomain(domain) |  | ||||||
|  |  | ||||||
|         if (baseUrl.isPresent()) { |  | ||||||
|             return fetcher.find(baseUrl.get(), request.getType().toString(), request.getThreePid()) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { |  | ||||||
|         Map<String, List<ThreePidMapping>> domains = new HashMap<>() |  | ||||||
|  |  | ||||||
|         for (ThreePidMapping mapping : mappings) { |  | ||||||
|             if (!StringUtils.equals("email", mapping.getMedium())) { |  | ||||||
|                 log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue()) |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Optional<String> domainOpt = getDomain(mapping.getValue()) |  | ||||||
|             if (!domainOpt.isPresent()) { |  | ||||||
|                 log.warn("No domain for 3PID {}", mapping.getValue()) |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             String domain = domainOpt.get() |  | ||||||
|             List<ThreePidMapping> domainMappings = domains.computeIfAbsent(domain, new Function<String, List<ThreePidMapping>>() { |  | ||||||
|  |  | ||||||
|                 @Override |  | ||||||
|                 List<ThreePidMapping> apply(String s) { |  | ||||||
|                     return new ArrayList<>() |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|             }) |  | ||||||
|             domainMappings.add(mapping) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Looking mappings across {} domains", domains.keySet().size()) |  | ||||||
|         ForkJoinPool pool = new ForkJoinPool() |  | ||||||
|         RecursiveTask<List<ThreePidMapping>> task = new RecursiveTask<List<ThreePidMapping>>() { |  | ||||||
|  |  | ||||||
|             @Override |  | ||||||
|             protected List<ThreePidMapping> compute() { |  | ||||||
|                 List<ThreePidMapping> mappingsFound = new ArrayList<>() |  | ||||||
|                 List<DomainBulkLookupTask> tasks = new ArrayList<>() |  | ||||||
|  |  | ||||||
|                 for (String domain : domains.keySet()) { |  | ||||||
|                     DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain)) |  | ||||||
|                     domainTask.fork() |  | ||||||
|                     tasks.add(domainTask) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 for (DomainBulkLookupTask task : tasks) { |  | ||||||
|                     mappingsFound.addAll(task.join()) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return mappingsFound |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         pool.submit(task) |  | ||||||
|         pool.shutdown() |  | ||||||
|  |  | ||||||
|         List<ThreePidMapping> mappingsFound = task.join() |  | ||||||
|         log.info("Found {} mappings overall", mappingsFound.size()) |  | ||||||
|         return mappingsFound |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class DomainBulkLookupTask extends RecursiveTask<List<ThreePidMapping>> { |  | ||||||
|  |  | ||||||
|         private String domain |  | ||||||
|         private List<ThreePidMapping> mappings |  | ||||||
|  |  | ||||||
|         DomainBulkLookupTask(String domain, List<ThreePidMapping> mappings) { |  | ||||||
|             this.domain = domain |  | ||||||
|             this.mappings = mappings |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         @Override |  | ||||||
|         protected List<ThreePidMapping> compute() { |  | ||||||
|             List<ThreePidMapping> domainMappings = new ArrayList<>() |  | ||||||
|  |  | ||||||
|             Optional<String> baseUrl = findIdentityServerForDomain(domain) |  | ||||||
|             if (!baseUrl.isPresent()) { |  | ||||||
|                 log.info("No usable Identity server for domain {}", domain) |  | ||||||
|             } else { |  | ||||||
|                 domainMappings.addAll(fetcher.find(baseUrl.get(), mappings)) |  | ||||||
|                 log.info("Found {} mappings in domain {}", domainMappings.size(), domain) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return domainMappings |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,87 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.provider |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.ForwardConfig |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class ForwarderProvider implements IThreePidProvider { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(ForwarderProvider.class) |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private ForwardConfig cfg |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private IRemoteIdentityServerFetcher fetcher |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isEnabled() { |  | ||||||
|         return true |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isLocal() { |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     int getPriority() { |  | ||||||
|         return 0 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     Optional<?> find(SingleLookupRequest request) { |  | ||||||
|         for (String root : cfg.getServers()) { |  | ||||||
|             Optional<?> answer = fetcher.find(root, request.getType(), request.getThreePid()) |  | ||||||
|             if (answer.isPresent()) { |  | ||||||
|                 return answer |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { |  | ||||||
|         List<ThreePidMapping> mappingsToDo = new ArrayList<>(mappings) |  | ||||||
|         List<ThreePidMapping> mappingsFoundGlobal = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         for (String root : cfg.getServers()) { |  | ||||||
|             log.info("{} mappings remaining: {}", mappingsToDo.size(), mappingsToDo) |  | ||||||
|             log.info("Querying {}", root) |  | ||||||
|             List<ThreePidMapping> mappingsFound = fetcher.find(root, mappingsToDo) |  | ||||||
|             log.info("{} returned {} mappings", root, mappingsFound.size()) |  | ||||||
|             mappingsFoundGlobal.addAll(mappingsFound) |  | ||||||
|             mappingsToDo.removeAll(mappingsFound) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return mappingsFoundGlobal |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,172 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.provider |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.LdapConfig |  | ||||||
| import io.kamax.mxisd.config.ServerConfig |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import org.apache.commons.lang.StringUtils |  | ||||||
| import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException |  | ||||||
| import org.apache.directory.api.ldap.model.cursor.EntryCursor |  | ||||||
| import org.apache.directory.api.ldap.model.entry.Attribute |  | ||||||
| import org.apache.directory.api.ldap.model.entry.Entry |  | ||||||
| import org.apache.directory.api.ldap.model.message.SearchScope |  | ||||||
| import org.apache.directory.ldap.client.api.LdapConnection |  | ||||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class LdapProvider implements IThreePidProvider { |  | ||||||
|  |  | ||||||
|     public static final String UID = "uid" |  | ||||||
|     public static final String MATRIX_ID = "mxid" |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(LdapProvider.class) |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private ServerConfig srvCfg |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private LdapConfig ldapCfg |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isEnabled() { |  | ||||||
|         return ldapCfg.getEnabled() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isLocal() { |  | ||||||
|         return true |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     int getPriority() { |  | ||||||
|         return 20 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Optional<String> lookup(LdapConnection conn, String medium, String value) { |  | ||||||
|         Optional<String> queryOpt = ldapCfg.getMapping(medium) |  | ||||||
|         if (!queryOpt.isPresent()) { |  | ||||||
|             log.warn("{} is not a configured 3PID type for LDAP lookup", medium) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         String searchQuery = queryOpt.get().replaceAll("%3pid", value) |  | ||||||
|         EntryCursor cursor = conn.search(ldapCfg.getBaseDn(), searchQuery, SearchScope.SUBTREE, ldapCfg.getAttribute()) |  | ||||||
|         try { |  | ||||||
|             while (cursor.next()) { |  | ||||||
|                 Entry entry = cursor.get() |  | ||||||
|                 log.info("Found possible match, DN: {}", entry.getDn().getName()) |  | ||||||
|  |  | ||||||
|                 Attribute attribute = entry.get(ldapCfg.getAttribute()) |  | ||||||
|                 if (attribute == null) { |  | ||||||
|                     log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute()) |  | ||||||
|                     continue |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 String data = attribute.get().toString() |  | ||||||
|                 if (data.length() < 1) { |  | ||||||
|                     log.info("DN {}: empty attribute {}, skipping", ldapCfg.getAttribute()) |  | ||||||
|                     continue |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 StringBuilder matrixId = new StringBuilder() |  | ||||||
|                 // TODO Should we turn this block into a map of functions? |  | ||||||
|                 if (StringUtils.equals(UID, ldapCfg.getType())) { |  | ||||||
|                     matrixId.append("@").append(data).append(":").append(srvCfg.getName()) |  | ||||||
|                 } else if (StringUtils.equals(MATRIX_ID, ldapCfg.getType())) { |  | ||||||
|                     matrixId.append(data) |  | ||||||
|                 } else { |  | ||||||
|                     log.warn("Bind was found but type {} is not supported", ldapCfg.getType()) |  | ||||||
|                     continue |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 log.info("DN {} is a valid match", entry.getDn().getName()) |  | ||||||
|                 return Optional.of(matrixId.toString()) |  | ||||||
|             } |  | ||||||
|         } catch (CursorLdapReferralException e) { |  | ||||||
|             log.warn("3PID {} is only available via referral, skipping", value) |  | ||||||
|         } finally { |  | ||||||
|             cursor.close() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     Optional<?> find(SingleLookupRequest request) { |  | ||||||
|         log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}") |  | ||||||
|  |  | ||||||
|         LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort(), ldapCfg.getTls()) |  | ||||||
|         try { |  | ||||||
|             conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword()) |  | ||||||
|  |  | ||||||
|             Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid()) |  | ||||||
|             if (mxid.isPresent()) { |  | ||||||
|                 return Optional.of([ |  | ||||||
|                         address   : request.getThreePid(), |  | ||||||
|                         medium    : request.getType(), |  | ||||||
|                         mxid      : mxid.get(), |  | ||||||
|                         not_before: 0, |  | ||||||
|                         not_after : 9223372036854775807, |  | ||||||
|                         ts        : 0 |  | ||||||
|                 ]) |  | ||||||
|             } |  | ||||||
|         } finally { |  | ||||||
|             conn.close() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("No match found") |  | ||||||
|         return Optional.empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { |  | ||||||
|         log.info("Looking up {} mappings", mappings.size()) |  | ||||||
|         List<ThreePidMapping> mappingsFound = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort()) |  | ||||||
|         try { |  | ||||||
|             conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword()) |  | ||||||
|  |  | ||||||
|             for (ThreePidMapping mapping : mappings) { |  | ||||||
|                 try { |  | ||||||
|                     Optional<String> mxid = lookup(conn, mapping.getMedium(), mapping.getValue()) |  | ||||||
|                     if (mxid.isPresent()) { |  | ||||||
|                         mapping.setMxid(mxid.get()) |  | ||||||
|                         mappingsFound.add(mapping) |  | ||||||
|                     } |  | ||||||
|                 } catch (IllegalArgumentException e) { |  | ||||||
|                     log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium()) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } finally { |  | ||||||
|             conn.close() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return mappingsFound |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,153 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.provider |  | ||||||
|  |  | ||||||
| import groovy.json.JsonException |  | ||||||
| import groovy.json.JsonOutput |  | ||||||
| import groovy.json.JsonSlurper |  | ||||||
| import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher |  | ||||||
| import org.apache.http.HttpEntity |  | ||||||
| import org.apache.http.HttpResponse |  | ||||||
| import org.apache.http.client.HttpClient |  | ||||||
| import org.apache.http.client.entity.EntityBuilder |  | ||||||
| import org.apache.http.client.methods.HttpPost |  | ||||||
| import org.apache.http.entity.ContentType |  | ||||||
| import org.apache.http.impl.client.HttpClients |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.context.annotation.Lazy |  | ||||||
| import org.springframework.context.annotation.Scope |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| @Scope("prototype") |  | ||||||
| @Lazy |  | ||||||
| public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher { |  | ||||||
|  |  | ||||||
|     public static final String THREEPID_TEST_MEDIUM = "email" |  | ||||||
|     public static final String THREEPID_TEST_ADDRESS = "john.doe@example.org" |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(RemoteIdentityServerFetcher.class) |  | ||||||
|  |  | ||||||
|     private JsonSlurper json = new JsonSlurper() |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     boolean isUsable(String remote) { |  | ||||||
|         try { |  | ||||||
|             HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( |  | ||||||
|                     "${remote}/_matrix/identity/api/v1/lookup?medium=${THREEPID_TEST_MEDIUM}&address=${THREEPID_TEST_ADDRESS}" |  | ||||||
|             ).openConnection() |  | ||||||
|             // TODO turn this into a configuration property |  | ||||||
|             rootSrvConn.setConnectTimeout(2000) |  | ||||||
|  |  | ||||||
|             if (rootSrvConn.getResponseCode() != 200) { |  | ||||||
|                 return false |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             def output = json.parseText(rootSrvConn.getInputStream().getText()) |  | ||||||
|             if (output['address']) { |  | ||||||
|                 return false |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return true |  | ||||||
|         } catch (IOException | JsonException e) { |  | ||||||
|             log.info("{} is not a usable Identity Server: {}", remote, e.getMessage()) |  | ||||||
|             return false |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     Optional<?> find(String remote, String type, String threePid) { |  | ||||||
|         log.info("Looking up {} 3PID {} using {}", type, threePid, remote) |  | ||||||
|  |  | ||||||
|         HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( |  | ||||||
|                 "${remote}/_matrix/identity/api/v1/lookup?medium=${type}&address=${threePid}" |  | ||||||
|         ).openConnection() |  | ||||||
|  |  | ||||||
|         try { |  | ||||||
|             def output = json.parseText(rootSrvConn.getInputStream().getText()) |  | ||||||
|             if (output['address']) { |  | ||||||
|                 log.info("Found 3PID mapping: {}", output) |  | ||||||
|                 return Optional.of(output) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             log.info("Empty 3PID mapping from {}", remote) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             log.warn("Error looking up 3PID mapping {}: {}", threePid, e.getMessage()) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } catch (JsonException e) { |  | ||||||
|             log.warn("Invalid JSON answer from {}", remote) |  | ||||||
|             return Optional.empty() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings) { |  | ||||||
|         List<ThreePidMapping> mappingsFound = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest() |  | ||||||
|         mappingRequest.setMappings(mappings) |  | ||||||
|  |  | ||||||
|         String url = "${remote}/_matrix/identity/api/v1/bulk_lookup" |  | ||||||
|         HttpClient client = HttpClients.createDefault() |  | ||||||
|         try { |  | ||||||
|             HttpPost request = new HttpPost(url) |  | ||||||
|             request.setEntity( |  | ||||||
|                     EntityBuilder.create() |  | ||||||
|                             .setText(JsonOutput.toJson(mappingRequest)) |  | ||||||
|                             .setContentType(ContentType.APPLICATION_JSON) |  | ||||||
|                             .build() |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|             HttpResponse response = client.execute(request) |  | ||||||
|             try { |  | ||||||
|                 if (response.getStatusLine().getStatusCode() != 200) { |  | ||||||
|                     log.info("Could not perform lookup at {} due to HTTP return code: {}", url, response.getStatusLine().getStatusCode()) |  | ||||||
|                     return mappingsFound |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 HttpEntity entity = response.getEntity() |  | ||||||
|                 if (entity != null) { |  | ||||||
|                     ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(entity.getContent().getText()) |  | ||||||
|                     for (List<String> mappingRaw : input.getThreepids()) { |  | ||||||
|                         ThreePidMapping mapping = new ThreePidMapping() |  | ||||||
|                         mapping.setMedium(mappingRaw.get(0)) |  | ||||||
|                         mapping.setValue(mappingRaw.get(1)) |  | ||||||
|                         mapping.setMxid(mappingRaw.get(2)) |  | ||||||
|                         mappingsFound.add(mapping) |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     log.info("HTTP response from {} was empty", remote) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return mappingsFound |  | ||||||
|             } finally { |  | ||||||
|                 response.close() |  | ||||||
|             } |  | ||||||
|         } finally { |  | ||||||
|             client.close() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.strategy |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.lookup.BulkLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.provider.IThreePidProvider |  | ||||||
|  |  | ||||||
| interface LookupStrategy { |  | ||||||
|  |  | ||||||
|     List<IThreePidProvider> getLocalProviders() |  | ||||||
|  |  | ||||||
|     Optional<?> find(SingleLookupRequest request) |  | ||||||
|  |  | ||||||
|     List<ThreePidMapping> find(BulkLookupRequest requests) |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,160 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.lookup.strategy |  | ||||||
|  |  | ||||||
| import edazdarevic.commons.net.CIDRUtils |  | ||||||
| import io.kamax.mxisd.config.RecursiveLookupConfig |  | ||||||
| import io.kamax.mxisd.lookup.ALookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.BulkLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest |  | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping |  | ||||||
| import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher |  | ||||||
| import io.kamax.mxisd.lookup.provider.IThreePidProvider |  | ||||||
| import org.slf4j.Logger |  | ||||||
| import org.slf4j.LoggerFactory |  | ||||||
| import org.springframework.beans.factory.InitializingBean |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| import java.util.function.Predicate |  | ||||||
| import java.util.stream.Collectors |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBean { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class) |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private RecursiveLookupConfig recursiveCfg |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private List<IThreePidProvider> providers |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private IBridgeFetcher bridge |  | ||||||
|  |  | ||||||
|     private List<CIDRUtils> allowedCidr = new ArrayList<>() |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     void afterPropertiesSet() throws Exception { |  | ||||||
|         log.info("Found ${providers.size()} providers") |  | ||||||
|  |  | ||||||
|         providers.sort(new Comparator<IThreePidProvider>() { |  | ||||||
|  |  | ||||||
|             @Override |  | ||||||
|             int compare(IThreePidProvider o1, IThreePidProvider o2) { |  | ||||||
|                 return Integer.compare(o2.getPriority(), o1.getPriority()) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         }) |  | ||||||
|  |  | ||||||
|         log.info("Recursive lookup enabled: {}", recursiveCfg.isEnabled()) |  | ||||||
|         for (String cidr : recursiveCfg.getAllowedCidr()) { |  | ||||||
|             log.info("{} is allowed for recursion", cidr) |  | ||||||
|             allowedCidr.add(new CIDRUtils(cidr)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     boolean isAllowedForRecursive(String source) { |  | ||||||
|         boolean canRecurse = false |  | ||||||
|  |  | ||||||
|         if (recursiveCfg.isEnabled()) { |  | ||||||
|             log.debug("Checking {} CIDRs for recursion", allowedCidr.size()) |  | ||||||
|             for (CIDRUtils cidr : allowedCidr) { |  | ||||||
|                 if (cidr.isInRange(source)) { |  | ||||||
|                     log.debug("{} is in range {}, allowing recursion", source, cidr.getNetworkAddress()) |  | ||||||
|                     canRecurse = true |  | ||||||
|                     break |  | ||||||
|                 } else { |  | ||||||
|                     log.debug("{} is not in range {}", source, cidr.getNetworkAddress()) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return canRecurse |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     List<IThreePidProvider> listUsableProviders(ALookupRequest request) { |  | ||||||
|         List<IThreePidProvider> usableProviders = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         boolean canRecurse = isAllowedForRecursive(request.getRequester()) |  | ||||||
|  |  | ||||||
|         log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse) |  | ||||||
|         for (IThreePidProvider provider : providers) { |  | ||||||
|             if (provider.isEnabled() && (provider.isLocal() || canRecurse)) { |  | ||||||
|                 usableProviders.add(provider) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return usableProviders |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<IThreePidProvider> getLocalProviders() { |  | ||||||
|         return providers.stream().filter(new Predicate<IThreePidProvider>() { |  | ||||||
|             @Override |  | ||||||
|             boolean test(IThreePidProvider iThreePidProvider) { |  | ||||||
|                 return iThreePidProvider.isEnabled() && iThreePidProvider.isLocal() |  | ||||||
|             } |  | ||||||
|         }).collect(Collectors.toList()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     Optional<?> find(SingleLookupRequest request) { |  | ||||||
|         for (IThreePidProvider provider : listUsableProviders(request)) { |  | ||||||
|             Optional<?> lookupDataOpt = provider.find(request) |  | ||||||
|             if (lookupDataOpt.isPresent()) { |  | ||||||
|                 return lookupDataOpt |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (recursiveCfg.getBridge().getEnabled() && (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))) { |  | ||||||
|             log.info("Using bridge failover for lookup") |  | ||||||
|             return bridge.find(request) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.empty() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     List<ThreePidMapping> find(BulkLookupRequest request) { |  | ||||||
|         List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings()) |  | ||||||
|         List<ThreePidMapping> mapFoundAll = new ArrayList<>() |  | ||||||
|  |  | ||||||
|         for (IThreePidProvider provider : listUsableProviders(request)) { |  | ||||||
|             if (mapToDo.isEmpty()) { |  | ||||||
|                 log.info("No more mappings to lookup") |  | ||||||
|                 break |  | ||||||
|             } else { |  | ||||||
|                 log.info("{} mappings remaining overall", mapToDo.size()) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             log.info("Using provider {} for remaining mappings", provider.getClass().getSimpleName()) |  | ||||||
|             List<ThreePidMapping> mapFound = provider.populate(mapToDo) |  | ||||||
|             log.info("Provider {} returned {} mappings", provider.getClass().getSimpleName(), mapFound.size()) |  | ||||||
|             mapFoundAll.addAll(mapFound) |  | ||||||
|             mapToDo.removeAll(mapFound) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return mapFoundAll |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,164 +0,0 @@ | |||||||
| package io.kamax.mxisd.mapping; |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.exception.BadRequestException; |  | ||||||
| import io.kamax.mxisd.lookup.ThreePid; |  | ||||||
| import org.apache.commons.lang.StringUtils; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
|  |  | ||||||
| import java.time.Instant; |  | ||||||
| import java.time.temporal.ChronoUnit; |  | ||||||
| import java.util.*; |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| public class MappingManager { |  | ||||||
|  |  | ||||||
|     private Logger log = LoggerFactory.getLogger(MappingManager.class); |  | ||||||
|  |  | ||||||
|     private Map<String, Session> threePidLookups = new WeakHashMap<>(); |  | ||||||
|     private Map<String, Session> sessions = new HashMap<>(); |  | ||||||
|     private Timer cleaner; |  | ||||||
|  |  | ||||||
|     MappingManager() { |  | ||||||
|         cleaner = new Timer(); |  | ||||||
|         cleaner.schedule(new TimerTask() { |  | ||||||
|             @Override |  | ||||||
|             public void run() { |  | ||||||
|                 List<Session> sList = new ArrayList<>(sessions.values()); |  | ||||||
|                 for (Session s : sList) { |  | ||||||
|                     if (s.timestamp.plus(24, ChronoUnit.HOURS).isBefore(Instant.now())) { // TODO config timeout |  | ||||||
|                         log.info("Session {} is obsolete, removing", s.sid); |  | ||||||
|  |  | ||||||
|                         sessions.remove(s.sid); |  | ||||||
|                         threePidLookups.remove(s.hash); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, 0, 10 * 1000);  // TODO config delay |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String create(MappingSession data) { |  | ||||||
|         String sid; |  | ||||||
|         do { |  | ||||||
|             sid = Long.toString(System.currentTimeMillis()); |  | ||||||
|         } while (sessions.containsKey(sid)); |  | ||||||
|  |  | ||||||
|         String threePidHash = data.getMedium() + data.getValue(); |  | ||||||
|         Session session = threePidLookups.get(threePidHash); |  | ||||||
|         if (session != null) { |  | ||||||
|             sid = session.sid; |  | ||||||
|         } else { |  | ||||||
|             // TODO perform some kind of validation |  | ||||||
|  |  | ||||||
|             session = new Session(sid, threePidHash, data); |  | ||||||
|             sessions.put(sid, session); |  | ||||||
|             threePidLookups.put(threePidHash, session); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Created new session {} to validate {} {}", sid, session.medium, session.address); |  | ||||||
|         return sid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void validate(String sid, String secret, String token) { |  | ||||||
|         Session s = sessions.get(sid); |  | ||||||
|         if (s == null || !StringUtils.equals(s.secret, secret)) { |  | ||||||
|             throw new BadRequestException("sid or secret are not valid"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // TODO actually check token |  | ||||||
|  |  | ||||||
|         s.isValidated = true; |  | ||||||
|         s.validationTimestamp = Instant.now(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Optional<ThreePid> getValidated(String sid, String secret) { |  | ||||||
|         Session s = sessions.get(sid); |  | ||||||
|         if (s != null && StringUtils.equals(s.secret, secret)) { |  | ||||||
|             return Optional.of(new ThreePid(s.medium, s.address, s.validationTimestamp)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Optional.empty(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void bind(String sid, String secret, String mxid) { |  | ||||||
|         Session s = sessions.get(sid); |  | ||||||
|         if (s == null || !StringUtils.equals(s.secret, secret)) { |  | ||||||
|             throw new BadRequestException("sid or secret are not valid"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Performed bind for mxid {}", mxid); |  | ||||||
|         // TODO perform bind, whatever it is |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class Session { |  | ||||||
|  |  | ||||||
|         private String sid; |  | ||||||
|         private String hash; |  | ||||||
|         private Instant timestamp; |  | ||||||
|         private Instant validationTimestamp; |  | ||||||
|         private boolean isValidated; |  | ||||||
|         private String secret; |  | ||||||
|         private String medium; |  | ||||||
|         private String address; |  | ||||||
|  |  | ||||||
|         public Session(String sid, String hash, MappingSession data) { |  | ||||||
|             this.sid = sid; |  | ||||||
|             this.hash = hash; |  | ||||||
|             timestamp = Instant.now(); |  | ||||||
|             validationTimestamp = Instant.now(); |  | ||||||
|             secret = data.getSecret(); |  | ||||||
|             medium = data.getMedium(); |  | ||||||
|             address = data.getValue(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Instant getTimestamp() { |  | ||||||
|             return timestamp; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void setTimestamp(Instant timestamp) { |  | ||||||
|             this.timestamp = timestamp; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Instant getValidationTimestamp() { |  | ||||||
|             return validationTimestamp; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void setValidationTimestamp(Instant validationTimestamp) { |  | ||||||
|             this.validationTimestamp = validationTimestamp; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public boolean isValidated() { |  | ||||||
|             return isValidated; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void setValidated(boolean validated) { |  | ||||||
|             isValidated = validated; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public String getSecret() { |  | ||||||
|             return secret; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void setSecret(String secret) { |  | ||||||
|             this.secret = secret; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,15 +0,0 @@ | |||||||
| package io.kamax.mxisd.mapping; |  | ||||||
|  |  | ||||||
| public interface MappingSession { |  | ||||||
|  |  | ||||||
|     String getServer(); |  | ||||||
|  |  | ||||||
|     String getSecret(); |  | ||||||
|  |  | ||||||
|     int getAttempt(); |  | ||||||
|  |  | ||||||
|     String getMedium(); |  | ||||||
|  |  | ||||||
|     String getValue(); |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| /* |  | ||||||
|  * mxisd - Matrix Identity Server Daemon |  | ||||||
|  * Copyright (C) 2017 Maxime Dor |  | ||||||
|  * |  | ||||||
|  * https://max.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.signature |  | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.ServerConfig |  | ||||||
| import io.kamax.mxisd.key.KeyManager |  | ||||||
| import net.i2p.crypto.eddsa.EdDSAEngine |  | ||||||
| import org.springframework.beans.factory.InitializingBean |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired |  | ||||||
| import org.springframework.stereotype.Component |  | ||||||
|  |  | ||||||
| import java.security.MessageDigest |  | ||||||
|  |  | ||||||
| @Component |  | ||||||
| class SignatureManager implements InitializingBean { |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private KeyManager keyMgr |  | ||||||
|  |  | ||||||
|     @Autowired |  | ||||||
|     private ServerConfig srvCfg |  | ||||||
|  |  | ||||||
|     private EdDSAEngine signEngine |  | ||||||
|  |  | ||||||
|     Map<?, ?> signMessage(String message) { |  | ||||||
|         byte[] signRaw = signEngine.signOneShot(message.getBytes()) |  | ||||||
|         String sign = Base64.getEncoder().encodeToString(signRaw) |  | ||||||
|         return [ |  | ||||||
|                 "${srvCfg.getName()}": [ |  | ||||||
|                         "ed25519:${keyMgr.getCurrentIndex()}": sign |  | ||||||
|                 ] |  | ||||||
|         ] |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     void afterPropertiesSet() throws Exception { |  | ||||||
|         signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())) |  | ||||||
|         signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex())) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -18,16 +18,16 @@ | |||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package io.kamax.mxisd | package io.kamax.mxisd; | ||||||
| 
 | 
 | ||||||
| import org.springframework.boot.SpringApplication | import org.springframework.boot.SpringApplication; | ||||||
| import org.springframework.boot.autoconfigure.SpringBootApplication | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||||
| 
 | 
 | ||||||
| @SpringBootApplication | @SpringBootApplication | ||||||
| class MatrixIdentityServerApplication { | public class MatrixIdentityServerApplication { | ||||||
| 
 | 
 | ||||||
|     static void main(String[] args) throws Exception { |     public static void main(String[] args) { | ||||||
|         SpringApplication.run(MatrixIdentityServerApplication.class, args) |         SpringApplication.run(MatrixIdentityServerApplication.class, args); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
							
								
								
									
										50
									
								
								src/main/java/io/kamax/mxisd/UserID.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/main/java/io/kamax/mxisd/UserID.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 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; | ||||||
|  |  | ||||||
|  | // FIXME consider integrating in matrix-java-sdk? | ||||||
|  | public class UserID { | ||||||
|  |  | ||||||
|  |     private String type; | ||||||
|  |     private String value; | ||||||
|  |  | ||||||
|  |     protected 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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getType() { | ||||||
|  |         return type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getValue() { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								src/main/java/io/kamax/mxisd/UserIdType.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/main/java/io/kamax/mxisd/UserIdType.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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; | ||||||
|  |  | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  |  | ||||||
|  | // FIXME consider integrating in matrix-java-sdk? | ||||||
|  | public enum UserIdType { | ||||||
|  |  | ||||||
|  |     Localpart("localpart"), | ||||||
|  |     MatrixID("mxid"), | ||||||
|  |     EmailLocalpart("email_localpart"), | ||||||
|  |     Email("email"); | ||||||
|  |  | ||||||
|  |     private String id; | ||||||
|  |  | ||||||
|  |     UserIdType(String id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean is(String id) { | ||||||
|  |         return StringUtils.equalsIgnoreCase(this.id, id); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								src/main/java/io/kamax/mxisd/as/AppServiceHandler.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/main/java/io/kamax/mxisd/as/AppServiceHandler.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | /* | ||||||
|  |  * 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.as; | ||||||
|  |  | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  | import io.kamax.matrix.MatrixID; | ||||||
|  | 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.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.notification.NotificationManager; | ||||||
|  | import io.kamax.mxisd.profile.ProfileManager; | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class AppServiceHandler { | ||||||
|  |  | ||||||
|  |     private final Logger log = LoggerFactory.getLogger(AppServiceHandler.class); | ||||||
|  |  | ||||||
|  |     private MatrixConfig cfg; | ||||||
|  |     private ProfileManager profiler; | ||||||
|  |     private NotificationManager notif; | ||||||
|  |     private Synapse synapse; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public AppServiceHandler(MatrixConfig cfg, ProfileManager profiler, NotificationManager notif, Synapse synapse) { | ||||||
|  |         this.cfg = cfg; | ||||||
|  |         this.profiler = profiler; | ||||||
|  |         this.notif = notif; | ||||||
|  |         this.synapse = synapse; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void processTransaction(List<JsonObject> eventsJson) { | ||||||
|  |         eventsJson.forEach(ev -> { | ||||||
|  |             if (!StringUtils.equals("m.room.member", GsonUtil.getStringOrNull(ev, "type"))) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (!StringUtils.equals("invite", GsonUtil.getStringOrNull(ev, "membership"))) { | ||||||
|  |                 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())) { | ||||||
|  |                     log.debug("Ignoring invite for {}: not a local user"); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 log.info("Got invite for {}", id); | ||||||
|  |  | ||||||
|  |                 boolean wasSent = false; | ||||||
|  |                 List<_ThreePid> tpids = profiler.getThreepids(mxid); | ||||||
|  |                 if (tpids.isEmpty()) { | ||||||
|  |                     log.info("No email found in identity stores for {}", id); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 for (_ThreePid tpid : tpids) { | ||||||
|  |                     if (!StringUtils.equals("email", tpid.getMedium())) { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     log.info("Found an email address 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); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     IMatrixIdInvite inv = new MatrixIdInvite(roomId, sender, mxid, tpid.getMedium(), tpid.getAddress(), properties); | ||||||
|  |                     notif.sendForInvite(inv); | ||||||
|  |                     wasSent = true; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 log.info("Was notification sent? {}", wasSent); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								src/main/java/io/kamax/mxisd/as/IMatrixIdInvite.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/main/java/io/kamax/mxisd/as/IMatrixIdInvite.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | /* | ||||||
|  |  * 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.as; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.invitation.IThreePidInvite; | ||||||
|  |  | ||||||
|  | public interface IMatrixIdInvite extends IThreePidInvite { | ||||||
|  |  | ||||||
|  |     _MatrixID getInvitee(); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								src/main/java/io/kamax/mxisd/as/MatrixIdInvite.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/main/java/io/kamax/mxisd/as/MatrixIdInvite.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | /* | ||||||
|  |  * 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.as; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | public class MatrixIdInvite implements IMatrixIdInvite { | ||||||
|  |  | ||||||
|  |     private String roomId; | ||||||
|  |     private _MatrixID sender; | ||||||
|  |     private _MatrixID invitee; | ||||||
|  |     private String medium; | ||||||
|  |     private String address; | ||||||
|  |     private Map<String, String> properties; | ||||||
|  |  | ||||||
|  |     public MatrixIdInvite(String roomId, _MatrixID sender, _MatrixID invitee, String medium, String address, Map<String, String> properties) { | ||||||
|  |         this.roomId = Objects.requireNonNull(roomId); | ||||||
|  |         this.sender = Objects.requireNonNull(sender); | ||||||
|  |         this.invitee = Objects.requireNonNull(invitee); | ||||||
|  |         this.medium = Objects.requireNonNull(medium); | ||||||
|  |         this.address = Objects.requireNonNull(address); | ||||||
|  |         this.properties = new HashMap<>(Objects.requireNonNull(properties)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public _MatrixID getSender() { | ||||||
|  |         return sender; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getMedium() { | ||||||
|  |         return medium; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getAddress() { | ||||||
|  |         return address; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public _MatrixID getInvitee() { | ||||||
|  |         return invitee; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getRoomId() { | ||||||
|  |         return roomId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Map<String, String> getProperties() { | ||||||
|  |         return properties; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								src/main/java/io/kamax/mxisd/auth/AuthManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/main/java/io/kamax/mxisd/auth/AuthManager.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 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.auth; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix.MatrixID; | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.matrix._ThreePid; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  | import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||||
|  | import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.invitation.InvitationManager; | ||||||
|  | import io.kamax.mxisd.lookup.ThreePidMapping; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | public class AuthManager { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(AuthManager.class); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private List<AuthenticatorProvider> providers = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private MatrixConfig mxCfg; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private InvitationManager invMgr; | ||||||
|  |  | ||||||
|  |     public UserAuthResult authenticate(String id, String password) { | ||||||
|  |         _MatrixID mxid = MatrixID.asAcceptable(id); | ||||||
|  |         for (AuthenticatorProvider provider : providers) { | ||||||
|  |             if (!provider.isEnabled()) { | ||||||
|  |                 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(); | ||||||
|  |                 } else if (UserIdType.MatrixID.is(result.getId().getType())) { | ||||||
|  |                     mxId = MatrixID.asAcceptable(result.getId().getValue()).getId(); | ||||||
|  |                 } else { | ||||||
|  |                     log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName()); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName()); | ||||||
|  |                 for (_ThreePid pid : result.getProfile().getThreePids()) { | ||||||
|  |                     authResult.withThreePid(pid.getMedium(), pid.getAddress()); | ||||||
|  |                 } | ||||||
|  |                 log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName()); | ||||||
|  |                 for (ThreePid pid : authResult.getThreePids()) { | ||||||
|  |                     log.info("Processing {} for {}", pid, id); | ||||||
|  |                     invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 invMgr.lookupMappingsForInvites(); | ||||||
|  |  | ||||||
|  |                 return authResult; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new UserAuthResult().failure(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/main/java/io/kamax/mxisd/auth/UserAuthResult.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/main/java/io/kamax/mxisd/auth/UserAuthResult.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.auth; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | public class UserAuthResult { | ||||||
|  |  | ||||||
|  |     private boolean success; | ||||||
|  |     private String displayName; | ||||||
|  |     private String photo; | ||||||
|  |     private Set<ThreePid> threePids = new HashSet<>(); | ||||||
|  |  | ||||||
|  |     public UserAuthResult failure() { | ||||||
|  |         success = false; | ||||||
|  |         displayName = null; | ||||||
|  |         photo = null; | ||||||
|  |         threePids.clear(); | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public UserAuthResult success(String displayName) { | ||||||
|  |         setSuccess(true); | ||||||
|  |         setDisplayName(displayName); | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isSuccess() { | ||||||
|  |         return success; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setSuccess(boolean success) { | ||||||
|  |         this.success = success; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getDisplayName() { | ||||||
|  |         return displayName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setDisplayName(String displayName) { | ||||||
|  |         this.displayName = displayName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getPhoto() { | ||||||
|  |         return photo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setPhoto(String photo) { | ||||||
|  |         this.photo = photo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public UserAuthResult withThreePid(String medium, String address) { | ||||||
|  |         threePids.add(new ThreePid(medium, address)); | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Set<ThreePid> getThreePids() { | ||||||
|  |         return Collections.unmodifiableSet(threePids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -18,11 +18,14 @@ | |||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package io.kamax.mxisd.exception | package io.kamax.mxisd.auth.provider; | ||||||
| 
 | 
 | ||||||
| import org.springframework.http.HttpStatus | import io.kamax.matrix._MatrixID; | ||||||
| import org.springframework.web.bind.annotation.ResponseStatus | 
 | ||||||
|  | public interface AuthenticatorProvider { | ||||||
|  | 
 | ||||||
|  |     boolean isEnabled(); | ||||||
|  | 
 | ||||||
|  |     BackendAuthResult authenticate(_MatrixID mxid, String password); | ||||||
| 
 | 
 | ||||||
| @ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED) |  | ||||||
| class NotImplementedException extends RuntimeException { |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,106 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 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.auth.provider; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.mxisd.UserID; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  |  | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | public class BackendAuthResult { | ||||||
|  |  | ||||||
|  |     public static class BackendAuthProfile { | ||||||
|  |  | ||||||
|  |         private String displayName; | ||||||
|  |         private Set<ThreePid> threePids = new HashSet<>(); | ||||||
|  |  | ||||||
|  |         public String getDisplayName() { | ||||||
|  |             return displayName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void setDisplayName(String displayName) { | ||||||
|  |             this.displayName = displayName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Set<ThreePid> getThreePids() { | ||||||
|  |             return threePids; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static BackendAuthResult failure() { | ||||||
|  |         BackendAuthResult r = new BackendAuthResult(); | ||||||
|  |         r.success = false; | ||||||
|  |         return r; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void fail() { | ||||||
|  |         success = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static BackendAuthResult success(String id, UserIdType type, String displayName) { | ||||||
|  |         return success(id, type.getId(), displayName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static BackendAuthResult success(String id, String type, String displayName) { | ||||||
|  |         BackendAuthResult r = new BackendAuthResult(); | ||||||
|  |         r.succeed(id, type, displayName); | ||||||
|  |         return r; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void succeed(String id, String type, String displayName) { | ||||||
|  |         this.success = true; | ||||||
|  |         this.id = new UserID(type, id); | ||||||
|  |         this.profile.displayName = displayName; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Boolean success; | ||||||
|  |     private UserID id; | ||||||
|  |     private BackendAuthProfile profile = new BackendAuthProfile(); | ||||||
|  |  | ||||||
|  |     public void setSuccess(boolean success) { | ||||||
|  |         this.success = success; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Boolean isSuccess() { | ||||||
|  |         return success; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public UserID getId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setId(UserID id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public BackendAuthProfile getProfile() { | ||||||
|  |         return profile; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public BackendAuthResult withThreePid(ThreePid threePid) { | ||||||
|  |         this.profile.threePids.add(threePid); | ||||||
|  |  | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										141
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecAuthStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecAuthStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | |||||||
|  | /* | ||||||
|  |  * 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.config.ExecConfig; | ||||||
|  | import io.kamax.mxisd.exception.InternalServerError; | ||||||
|  | import org.apache.commons.io.IOUtils; | ||||||
|  | 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 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.stream.Collectors; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class ExecAuthStore extends ExecStore implements AuthenticatorProvider { | ||||||
|  |  | ||||||
|  |     private final transient 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())); | ||||||
|  |  | ||||||
|  |         ProcessExecutor psExec = new ProcessExecutor().readOutput(true); | ||||||
|  |  | ||||||
|  |         List<String> args = new ArrayList<>(); | ||||||
|  |         args.add(cfg.getCommand()); | ||||||
|  |         args.addAll(cfg.getArgs().stream().map(arg -> arg | ||||||
|  |                 .replace(cfg.getToken().getLocalpart(), uId.getLocalPart()) | ||||||
|  |                 .replace(cfg.getToken().getDomain(), uId.getDomain()) | ||||||
|  |                 .replace(cfg.getToken().getMxid(), uId.getId()) | ||||||
|  |                 .replace(cfg.getToken().getPassword(), password) | ||||||
|  |         ).collect(Collectors.toList())); | ||||||
|  |         psExec.command(args); | ||||||
|  |  | ||||||
|  |         psExec.environment(new HashMap<>(cfg.getEnv()).entrySet().stream().peek(e -> { | ||||||
|  |             e.setValue(e.getValue().replace(cfg.getToken().getLocalpart(), uId.getLocalPart())); | ||||||
|  |             e.setValue(e.getValue().replace(cfg.getToken().getDomain(), uId.getDomain())); | ||||||
|  |             e.setValue(e.getValue().replace(cfg.getToken().getMxid(), uId.getId())); | ||||||
|  |             e.setValue(e.getValue().replace(cfg.getToken().getPassword(), password)); | ||||||
|  |         }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); | ||||||
|  |  | ||||||
|  |         if (StringUtils.isNotBlank(cfg.getInput())) { | ||||||
|  |             if (StringUtils.equals("json", cfg.getInput())) { | ||||||
|  |                 JsonObject input = new JsonObject(); | ||||||
|  |                 input.addProperty("localpart", uId.getLocalPart()); | ||||||
|  |                 input.addProperty("mxid", uId.getId()); | ||||||
|  |                 input.addProperty("password", password); | ||||||
|  |                 psExec.redirectInput(IOUtils.toInputStream(GsonUtil.get().toJson(input), StandardCharsets.UTF_8)); | ||||||
|  |             } else { | ||||||
|  |                 throw new InternalServerError(cfg.getInput() + " is not a valid executable input format"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             log.info("Executing {}", cfg.getCommand()); | ||||||
|  |             ProcessResult psResult = psExec.execute(); | ||||||
|  |             result.setExitStatus(psResult.getExitValue()); | ||||||
|  |             String output = psResult.outputUTF8(); | ||||||
|  |  | ||||||
|  |             log.info("Exit status: {}", result.getExitStatus()); | ||||||
|  |             if (cfg.getExit().getSuccess().contains(result.getExitStatus())) { | ||||||
|  |                 result.setSuccess(true); | ||||||
|  |                 if (result.isSuccess()) { | ||||||
|  |                     if (StringUtils.equals("json", cfg.getOutput())) { | ||||||
|  |                         JsonObject data = GsonUtil.parseObj(output); | ||||||
|  |                         GsonUtil.findPrimitive(data, "success") | ||||||
|  |                                 .map(JsonPrimitive::getAsBoolean) | ||||||
|  |                                 .ifPresent(result::setSuccess); | ||||||
|  |                         GsonUtil.findObj(data, "profile") | ||||||
|  |                                 .flatMap(p -> GsonUtil.findString(p, "display_name")) | ||||||
|  |                                 .ifPresent(v -> result.getProfile().setDisplayName(v)); | ||||||
|  |                     } else { | ||||||
|  |                         log.debug("Command output:{}{}", "\n", output); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else if (cfg.getExit().getFailure().contains(result.getExitStatus())) { | ||||||
|  |                 log.debug("{} stdout:{}{}", cfg.getCommand(), "\n", output); | ||||||
|  |                 result.setSuccess(false); | ||||||
|  |             } else { | ||||||
|  |                 log.error("{} stdout:{}{}", cfg.getCommand(), "\n", output); | ||||||
|  |                 throw new InternalServerError("Exec auth command returned with unexpected exit status"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } catch (IOException | InterruptedException | TimeoutException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * 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.controller.directory.v1.io.UserDirectorySearchResult; | ||||||
|  | import io.kamax.mxisd.directory.IDirectoryProvider; | ||||||
|  | import io.kamax.mxisd.exception.NotImplementedException; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class ExecDirectoryStore extends ExecStore implements IDirectoryProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchByDisplayName(String query) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchBy3pid(String query) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | /* | ||||||
|  |  * 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.exception.NotImplementedException; | ||||||
|  | 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.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class ExecIdentityStore extends ExecStore implements IThreePidProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isLocal() { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getPriority() { | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,55 @@ | |||||||
|  | /* | ||||||
|  |  * 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.mxisd.exception.NotImplementedException; | ||||||
|  | import io.kamax.mxisd.profile.ProfileProvider; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class ExecProfileStore extends ExecStore implements ProfileProvider { | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<String> getDisplayName(_MatrixID userId) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<_ThreePid> getThreepids(_MatrixID userId) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<String> getRoles(_MatrixID userId) { | ||||||
|  |         throw new NotImplementedException(this.getClass().getName()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecStore.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/main/java/io/kamax/mxisd/backend/exec/ExecStore.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | public abstract class ExecStore { | ||||||
|  |  | ||||||
|  |     // no-op | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,163 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.firebase; | ||||||
|  |  | ||||||
|  | import com.google.firebase.auth.UserInfo; | ||||||
|  | import com.google.i18n.phonenumbers.NumberParseException; | ||||||
|  | import com.google.i18n.phonenumbers.PhoneNumberUtil; | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.matrix.ThreePidMedium; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  | import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||||
|  | import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.CountDownLatch; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | public class GoogleFirebaseAuthenticator extends GoogleFirebaseBackend implements AuthenticatorProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class); | ||||||
|  |  | ||||||
|  |     private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); | ||||||
|  |  | ||||||
|  |     public GoogleFirebaseAuthenticator(boolean isEnabled, String credsPath, String db) { | ||||||
|  |         super(isEnabled, "AuthenticationProvider", credsPath, db); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void waitOnLatch(BackendAuthResult result, CountDownLatch l, String purpose) { | ||||||
|  |         try { | ||||||
|  |             l.await(30, TimeUnit.SECONDS); | ||||||
|  |         } catch (InterruptedException e) { | ||||||
|  |             log.warn("Interrupted while waiting for " + purpose); | ||||||
|  |             result.fail(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void toEmail(BackendAuthResult result, String email) { | ||||||
|  |         if (StringUtils.isBlank(email)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), email)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void toMsisdn(BackendAuthResult result, String phoneNumber) { | ||||||
|  |         if (StringUtils.isBlank(phoneNumber)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             String number = phoneUtil.format( | ||||||
|  |                     phoneUtil.parse( | ||||||
|  |                             phoneNumber, | ||||||
|  |                             null // No default region | ||||||
|  |                     ), | ||||||
|  |                     PhoneNumberUtil.PhoneNumberFormat.E164 | ||||||
|  |             ).substring(1); // We want without the leading + | ||||||
|  |             result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), number)); | ||||||
|  |         } catch (NumberParseException e) { | ||||||
|  |             log.warn("Invalid phone number: {}", phoneNumber); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void waitOnLatch(CountDownLatch l) { | ||||||
|  |         try { | ||||||
|  |             l.await(30, TimeUnit.SECONDS); | ||||||
|  |         } catch (InterruptedException e) { | ||||||
|  |             log.warn("Interrupted while waiting for Firebase auth check"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public BackendAuthResult authenticate(_MatrixID mxid, String password) { | ||||||
|  |         if (!isEnabled()) { | ||||||
|  |             throw new IllegalStateException(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         log.info("Trying to authenticate {}", mxid); | ||||||
|  |  | ||||||
|  |         final BackendAuthResult result = BackendAuthResult.failure(); | ||||||
|  |  | ||||||
|  |         String localpart = mxid.getLocalPart(); | ||||||
|  |         CountDownLatch l = new CountDownLatch(1); | ||||||
|  |         getFirebase().verifyIdToken(password).addOnSuccessListener(token -> { | ||||||
|  |             try { | ||||||
|  |                 if (!StringUtils.equals(localpart, token.getUid())) { | ||||||
|  |                     log.info("Failure to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", mxid, localpart, token.getUid()); | ||||||
|  |                     result.fail(); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), token.getName()); | ||||||
|  |                 log.info("{} was successfully authenticated", mxid); | ||||||
|  |                 log.info("Fetching profile for {}", mxid); | ||||||
|  |                 CountDownLatch userRecordLatch = new CountDownLatch(1); | ||||||
|  |                 getFirebase().getUser(token.getUid()).addOnSuccessListener(user -> { | ||||||
|  |                     try { | ||||||
|  |                         toEmail(result, user.getEmail()); | ||||||
|  |                         toMsisdn(result, user.getPhoneNumber()); | ||||||
|  |  | ||||||
|  |                         for (UserInfo info : user.getProviderData()) { | ||||||
|  |                             toEmail(result, info.getEmail()); | ||||||
|  |                             toMsisdn(result, info.getPhoneNumber()); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         log.info("Got {} 3PIDs in profile", result.getProfile().getThreePids().size()); | ||||||
|  |                     } finally { | ||||||
|  |                         userRecordLatch.countDown(); | ||||||
|  |                     } | ||||||
|  |                 }).addOnFailureListener(e -> { | ||||||
|  |                     try { | ||||||
|  |                         log.warn("Unable to fetch Firebase user profile for {}", mxid); | ||||||
|  |                         result.fail(); | ||||||
|  |                     } finally { | ||||||
|  |                         userRecordLatch.countDown(); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 waitOnLatch(result, userRecordLatch, "Firebase user profile"); | ||||||
|  |             } finally { | ||||||
|  |                 l.countDown(); | ||||||
|  |             } | ||||||
|  |         }).addOnFailureListener(e -> { | ||||||
|  |             try { | ||||||
|  |                 if (e instanceof IllegalArgumentException) { | ||||||
|  |                     log.info("Failure to authenticate {}: invalid firebase token", mxid); | ||||||
|  |                 } else { | ||||||
|  |                     log.info("Failure to authenticate {}: {}", mxid, e.getMessage(), e); | ||||||
|  |                     log.info("Exception", e); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 result.fail(); | ||||||
|  |             } finally { | ||||||
|  |                 l.countDown(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         waitOnLatch(result, l, "Firebase auth check"); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,88 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.firebase; | ||||||
|  |  | ||||||
|  | import com.google.firebase.FirebaseApp; | ||||||
|  | import com.google.firebase.FirebaseOptions; | ||||||
|  | import com.google.firebase.auth.FirebaseAuth; | ||||||
|  | import com.google.firebase.auth.FirebaseCredential; | ||||||
|  | import com.google.firebase.auth.FirebaseCredentials; | ||||||
|  | import com.google.firebase.database.FirebaseDatabase; | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.io.FileInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | public class GoogleFirebaseBackend { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(GoogleFirebaseBackend.class); | ||||||
|  |  | ||||||
|  |     private boolean isEnabled; | ||||||
|  |     private FirebaseAuth fbAuth; | ||||||
|  |     protected FirebaseDatabase fbDb; | ||||||
|  |  | ||||||
|  |     GoogleFirebaseBackend(boolean isEnabled, String name, String credsPath, String db) { | ||||||
|  |         this.isEnabled = isEnabled; | ||||||
|  |         if (!isEnabled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             FirebaseApp fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), name); | ||||||
|  |             fbAuth = FirebaseAuth.getInstance(fbApp); | ||||||
|  |             FirebaseDatabase.getInstance(fbApp); | ||||||
|  |  | ||||||
|  |             log.info("Google Firebase Authentication is ready"); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new RuntimeException("Error when initializing Firebase", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private FirebaseCredential getCreds(String credsPath) throws IOException { | ||||||
|  |         if (StringUtils.isNotBlank(credsPath)) { | ||||||
|  |             return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath)); | ||||||
|  |         } else { | ||||||
|  |             return FirebaseCredentials.applicationDefault(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private FirebaseOptions getOpts(String credsPath, String db) throws IOException { | ||||||
|  |         if (StringUtils.isBlank(db)) { | ||||||
|  |             throw new IllegalArgumentException("Firebase database is not configured"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new FirebaseOptions.Builder() | ||||||
|  |                 .setCredential(getCreds(credsPath)) | ||||||
|  |                 .setDatabaseUrl(db) | ||||||
|  |                 .build(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     FirebaseAuth getFirebase() { | ||||||
|  |         return fbAuth; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return isEnabled; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,132 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.firebase; | ||||||
|  |  | ||||||
|  | import com.google.firebase.auth.UserRecord; | ||||||
|  | import com.google.firebase.tasks.OnFailureListener; | ||||||
|  | import com.google.firebase.tasks.OnSuccessListener; | ||||||
|  | import io.kamax.matrix.MatrixID; | ||||||
|  | import io.kamax.matrix.ThreePidMedium; | ||||||
|  | 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.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.concurrent.CountDownLatch; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | public class GoogleFirebaseProvider extends GoogleFirebaseBackend implements IThreePidProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class); | ||||||
|  |     private String domain; | ||||||
|  |  | ||||||
|  |     public GoogleFirebaseProvider(boolean isEnabled, String credsPath, String db, String domain) { | ||||||
|  |         super(isEnabled, "ThreePidProvider", credsPath, db); | ||||||
|  |         this.domain = domain; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private String getMxid(UserRecord record) { | ||||||
|  |         return new MatrixID(record.getUid(), domain).getId(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isLocal() { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getPriority() { | ||||||
|  |         return 25; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void waitOnLatch(CountDownLatch l) { | ||||||
|  |         try { | ||||||
|  |             l.await(30, TimeUnit.SECONDS); | ||||||
|  |         } catch (InterruptedException e) { | ||||||
|  |             log.warn("Interrupted while waiting for Firebase auth check"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Optional<UserRecord> findInternal(String medium, String address) { | ||||||
|  |         final UserRecord[] r = new UserRecord[1]; | ||||||
|  |         CountDownLatch l = new CountDownLatch(1); | ||||||
|  |  | ||||||
|  |         OnSuccessListener<UserRecord> success = result -> { | ||||||
|  |             log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid()); | ||||||
|  |             r[0] = result; | ||||||
|  |             l.countDown(); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         OnFailureListener failure = e -> { | ||||||
|  |             log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage()); | ||||||
|  |             r[0] = null; | ||||||
|  |             l.countDown(); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if (ThreePidMedium.Email.is(medium)) { | ||||||
|  |             log.info("Performing E-mail 3PID lookup for {}", address); | ||||||
|  |             getFirebase().getUserByEmail(address) | ||||||
|  |                     .addOnSuccessListener(success) | ||||||
|  |                     .addOnFailureListener(failure); | ||||||
|  |             waitOnLatch(l); | ||||||
|  |         } else if (ThreePidMedium.PhoneNumber.is(medium)) { | ||||||
|  |             log.info("Performing msisdn 3PID lookup for {}", address); | ||||||
|  |             getFirebase().getUserByPhoneNumber(address) | ||||||
|  |                     .addOnSuccessListener(success) | ||||||
|  |                     .addOnFailureListener(failure); | ||||||
|  |             waitOnLatch(l); | ||||||
|  |         } else { | ||||||
|  |             log.info("{} is not a supported 3PID medium", medium); | ||||||
|  |             r[0] = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Optional.ofNullable(r[0]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||||
|  |         Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid()); | ||||||
|  |         return urOpt.map(userRecord -> new SingleLookupReply(request, getMxid(userRecord))); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||||
|  |         List<ThreePidMapping> results = new ArrayList<>(); | ||||||
|  |         mappings.parallelStream().forEach(o -> { | ||||||
|  |             Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue()); | ||||||
|  |             if (urOpt.isPresent()) { | ||||||
|  |                 ThreePidMapping result = new ThreePidMapping(); | ||||||
|  |                 result.setMedium(o.getMedium()); | ||||||
|  |                 result.setValue(o.getValue()); | ||||||
|  |                 result.setMxid(getMxid(urOpt.get())); | ||||||
|  |                 results.add(result); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         return results; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										170
									
								
								src/main/java/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/main/java/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.ldap; | ||||||
|  |  | ||||||
|  | import com.google.i18n.phonenumbers.NumberParseException; | ||||||
|  | import com.google.i18n.phonenumbers.PhoneNumberUtil; | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.matrix.ThreePidMedium; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  | 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.util.GsonUtil; | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.EntryCursor; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Attribute; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Entry; | ||||||
|  | import org.apache.directory.api.ldap.model.exception.LdapException; | ||||||
|  | import org.apache.directory.api.ldap.model.message.SearchScope; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapConnection; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class); | ||||||
|  |  | ||||||
|  |     private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return getCfg().isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Optional<String> getMsisdn(String phoneNumber) { | ||||||
|  |         try { // FIXME export into dedicated ThreePid class within SDK (copy from Firebase Auth) | ||||||
|  |             return Optional.of(phoneUtil.format( | ||||||
|  |                     phoneUtil.parse( | ||||||
|  |                             phoneNumber, | ||||||
|  |                             null // No default region | ||||||
|  |                     ), | ||||||
|  |                     PhoneNumberUtil.PhoneNumberFormat.E164 | ||||||
|  |             ).substring(1)); // We want without the leading + | ||||||
|  |         } catch (NumberParseException e) { | ||||||
|  |             log.warn("Invalid phone number: {}", phoneNumber); | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public BackendAuthResult authenticate(_MatrixID mxid, String password) { | ||||||
|  |         log.info("Performing auth for {}", mxid); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             bind(conn); | ||||||
|  |  | ||||||
|  |             String uidType = getAt().getUid().getType(); | ||||||
|  |             String userFilterValue = StringUtils.equals(LdapBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId(); | ||||||
|  |             if (StringUtils.isBlank(userFilterValue)) { | ||||||
|  |                 log.warn("Username is empty, failing auth"); | ||||||
|  |                 return BackendAuthResult.failure(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")"; | ||||||
|  |             userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter()); | ||||||
|  |  | ||||||
|  |             Set<String> attributes = new HashSet<>(); | ||||||
|  |             attributes.add(getUidAtt()); | ||||||
|  |             attributes.add(getAt().getName()); | ||||||
|  |             getAt().getThreepid().forEach((k, v) -> attributes.addAll(v)); | ||||||
|  |             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)) { | ||||||
|  |                 while (cursor.next()) { | ||||||
|  |                     Entry entry = cursor.get(); | ||||||
|  |                     String dn = entry.getDn().getName(); | ||||||
|  |                     log.info("Checking possible match, DN: {}", dn); | ||||||
|  |  | ||||||
|  |                     if (!getAttribute(entry, getUidAtt()).isPresent()) { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     log.info("Attempting authentication on LDAP for {}", dn); | ||||||
|  |                     try { | ||||||
|  |                         conn.bind(entry.getDn(), password); | ||||||
|  |                     } catch (LdapException e) { | ||||||
|  |                         log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage()); | ||||||
|  |                         return BackendAuthResult.failure(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     Attribute nameAttribute = entry.get(getAt().getName()); | ||||||
|  |                     String name = nameAttribute != null ? nameAttribute.get().toString() : null; | ||||||
|  |  | ||||||
|  |                     log.info("Authentication successful for {}", entry.getDn().getName()); | ||||||
|  |                     log.info("DN {} is a valid match", dn); | ||||||
|  |  | ||||||
|  |                     // TODO should we canonicalize the MXID? | ||||||
|  |                     BackendAuthResult result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name); | ||||||
|  |                     log.info("Processing 3PIDs for profile"); | ||||||
|  |                     getAt().getThreepid().forEach((k, v) -> { | ||||||
|  |                         log.info("Processing 3PID type {}", k); | ||||||
|  |                         v.forEach(attId -> { | ||||||
|  |                             List<String> values = getAttributes(entry, attId); | ||||||
|  |                             log.info("\tAttribute {} has {} value(s)", attId, values.size()); | ||||||
|  |                             getAttributes(entry, attId).forEach(tpidValue -> { | ||||||
|  |                                 if (ThreePidMedium.PhoneNumber.is(k)) { | ||||||
|  |                                     tpidValue = getMsisdn(tpidValue).orElse(tpidValue); | ||||||
|  |                                 } | ||||||
|  |                                 result.withThreePid(new ThreePid(k, tpidValue)); | ||||||
|  |                             }); | ||||||
|  |                         }); | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     log.info("Found {} 3PIDs", result.getProfile().getThreePids().size()); | ||||||
|  |                     return result; | ||||||
|  |                 } | ||||||
|  |             } 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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										172
									
								
								src/main/java/io/kamax/mxisd/backend/ldap/LdapBackend.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								src/main/java/io/kamax/mxisd/backend/ldap/LdapBackend.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 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.ldap; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.LdapConfig; | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Attribute; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.AttributeUtils; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Entry; | ||||||
|  | import org.apache.directory.api.ldap.model.exception.LdapException; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapConnection; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import javax.naming.NamingEnumeration; | ||||||
|  | import javax.naming.NamingException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | public abstract class LdapBackend { | ||||||
|  |  | ||||||
|  |     public static final String UID = "uid"; | ||||||
|  |     public static final String MATRIX_ID = "mxid"; | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(LdapBackend.class); | ||||||
|  |  | ||||||
|  |     private LdapConfig cfg; | ||||||
|  |     private MatrixConfig mxCfg; | ||||||
|  |  | ||||||
|  |     public LdapBackend(LdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         this.cfg = cfg; | ||||||
|  |         this.mxCfg = mxCfg; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected LdapConfig getCfg() { | ||||||
|  |         return cfg; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected String getBaseDn() { | ||||||
|  |         return cfg.getConnection().getBaseDn(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected LdapConfig.Attribute getAt() { | ||||||
|  |         return cfg.getAttribute(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected String getUidAtt() { | ||||||
|  |         return getAt().getUid().getValue(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected synchronized LdapConnection getConn() { | ||||||
|  |         return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected void bind(LdapConnection conn) throws LdapException { | ||||||
|  |         if (StringUtils.isBlank(cfg.getConnection().getBindDn()) && StringUtils.isBlank(cfg.getConnection().getBindPassword())) { | ||||||
|  |             conn.anonymousBind(); | ||||||
|  |         } else { | ||||||
|  |             conn.bind(cfg.getConnection().getBindDn(), cfg.getConnection().getBindPassword()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected String buildWithFilter(String base, String filter) { | ||||||
|  |         if (StringUtils.isBlank(filter)) { | ||||||
|  |             return base; | ||||||
|  |         } else { | ||||||
|  |             return "(&" + filter + base + ")"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static String buildOrQuery(String value, List<String> attributes) { | ||||||
|  |         if (attributes.size() < 1) { | ||||||
|  |             throw new IllegalArgumentException(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         StringBuilder builder = new StringBuilder(); | ||||||
|  |         builder.append("(|"); | ||||||
|  |         attributes.forEach(s -> { | ||||||
|  |             builder.append("("); | ||||||
|  |             builder.append(s).append("=").append(value).append(")"); | ||||||
|  |         }); | ||||||
|  |         builder.append(")"); | ||||||
|  |         return builder.toString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static String buildOrQuery(String value, String... attributes) { | ||||||
|  |         return buildOrQuery(value, Arrays.asList(attributes)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String buildOrQueryWithFilter(String filter, String value, String... attributes) { | ||||||
|  |         return buildWithFilter(buildOrQuery(value, attributes), filter); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String buildMatrixIdFromUid(String uid) { | ||||||
|  |         String uidType = getCfg().getAttribute().getUid().getType(); | ||||||
|  |         if (StringUtils.equals(UID, uidType)) { | ||||||
|  |             return "@" + uid + ":" + mxCfg.getDomain(); | ||||||
|  |         } else if (StringUtils.equals(MATRIX_ID, uidType)) { | ||||||
|  |             return uid; | ||||||
|  |         } else { | ||||||
|  |             throw new IllegalArgumentException("Bind type " + uidType + " is not supported"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String buildUidFromMatrixId(_MatrixID mxId) { | ||||||
|  |         String uidType = getCfg().getAttribute().getUid().getType(); | ||||||
|  |         if (StringUtils.equals(UID, uidType)) { | ||||||
|  |             return mxId.getLocalPart(); | ||||||
|  |         } else if (StringUtils.equals(MATRIX_ID, uidType)) { | ||||||
|  |             return mxId.getId(); | ||||||
|  |         } else { | ||||||
|  |             throw new IllegalArgumentException("Bind type " + uidType + " is not supported"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Optional<String> getAttribute(Entry entry, String attName) { | ||||||
|  |         Attribute attribute = entry.get(attName); | ||||||
|  |         if (attribute == null) { | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         String value = attribute.get().toString(); | ||||||
|  |         if (StringUtils.isBlank(value)) { | ||||||
|  |             log.info("DN {}: empty attribute {}, skipping", attName); | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Optional.of(value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public List<String> getAttributes(Entry entry, String attName) { | ||||||
|  |         List<String> values = new ArrayList<>(); | ||||||
|  |         javax.naming.directory.Attribute att = AttributeUtils.toAttributes(entry).get(attName); | ||||||
|  |         if (att == null) { | ||||||
|  |             return values; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             NamingEnumeration<?> list = att.getAll(); | ||||||
|  |             while (list.hasMore()) { | ||||||
|  |                 values.add(list.next().toString()); | ||||||
|  |             } | ||||||
|  |         } catch (NamingException e) { | ||||||
|  |             log.warn("Error while processing LDAP attribute {}, result could be incomplete!", attName, e); | ||||||
|  |         } | ||||||
|  |         return values; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,123 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.ldap; | ||||||
|  |  | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.LdapConfig; | ||||||
|  | import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; | ||||||
|  | import io.kamax.mxisd.directory.IDirectoryProvider; | ||||||
|  | import io.kamax.mxisd.exception.InternalServerError; | ||||||
|  | import io.kamax.mxisd.util.GsonUtil; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.EntryCursor; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Entry; | ||||||
|  | import org.apache.directory.api.ldap.model.exception.LdapException; | ||||||
|  | import org.apache.directory.api.ldap.model.message.SearchScope; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapConnection; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public LdapDirectoryProvider(LdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return getCfg().isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected UserDirectorySearchResult search(String query, List<String> attributes) { | ||||||
|  |         UserDirectorySearchResult result = new UserDirectorySearchResult(); | ||||||
|  |         result.setLimited(false); | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             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)) { | ||||||
|  |                 while (cursor.next()) { | ||||||
|  |                     Entry entry = cursor.get(); | ||||||
|  |                     log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||||
|  |                     getAttribute(entry, getUidAtt()).ifPresent(uid -> { | ||||||
|  |                         log.info("DN {} is a valid match", entry.getDn().getName()); | ||||||
|  |                         try { | ||||||
|  |                             UserDirectorySearchResult.Result entryResult = new UserDirectorySearchResult.Result(); | ||||||
|  |                             entryResult.setUserId(buildMatrixIdFromUid(uid)); | ||||||
|  |                             getAttribute(entry, atCfg.getName()).ifPresent(entryResult::setDisplayName); | ||||||
|  |                             result.addResult(entryResult); | ||||||
|  |                         } catch (IllegalArgumentException e) { | ||||||
|  |                             log.warn("Bind was found but type {} is not supported", atCfg.getUid().getType()); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (CursorLdapReferralException e) { | ||||||
|  |             log.warn("An entry is only available via referral, skipping"); | ||||||
|  |         } catch (IOException | LdapException | CursorException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchByDisplayName(String query) { | ||||||
|  |         log.info("Performing LDAP directory search on display name using '{}'", query); | ||||||
|  |         List<String> attributes = new ArrayList<>(); | ||||||
|  |         attributes.add(getAt().getName()); | ||||||
|  |         attributes.addAll(getCfg().getDirectory().getAttribute().getOther()); | ||||||
|  |         return search(query, attributes); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchBy3pid(String query) { | ||||||
|  |         log.info("Performing LDAP directory search on 3PIDs using '{}'", query); | ||||||
|  |         List<String> attributes = new ArrayList<>(); | ||||||
|  |         attributes.add(getAt().getName()); | ||||||
|  |         getCfg().getAttribute().getThreepid().forEach((k, v) -> attributes.addAll(v)); | ||||||
|  |         return search(query, attributes); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,157 @@ | |||||||
|  | /* | ||||||
|  |  * 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.ldap; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.matrix._ThreePid; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.LdapConfig; | ||||||
|  | import io.kamax.mxisd.exception.InternalServerError; | ||||||
|  | import io.kamax.mxisd.profile.ProfileProvider; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.EntryCursor; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Entry; | ||||||
|  | import org.apache.directory.api.ldap.model.exception.LdapException; | ||||||
|  | import org.apache.directory.api.ldap.model.message.SearchScope; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapConnection; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class LdapProfileProvider extends LdapBackend implements ProfileProvider { | ||||||
|  |  | ||||||
|  |     private transient Logger log = LoggerFactory.getLogger(LdapProfileProvider.class); | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public LdapProfileProvider(LdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return getCfg().isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<String> getDisplayName(_MatrixID userId) { | ||||||
|  |         String uid = buildUidFromMatrixId(userId); | ||||||
|  |         log.info("Searching for display name of {}:", uid); | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             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())) { | ||||||
|  |                 while (cursor.next()) { | ||||||
|  |                     Entry entry = cursor.get(); | ||||||
|  |                     log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||||
|  |                     Optional<String> v = getAttribute(entry, getAt().getName()).flatMap(id -> { | ||||||
|  |                         log.info("DN {} is a valid match", entry.getDn().getName()); | ||||||
|  |                         try { | ||||||
|  |                             return getAttribute(entry, getAt().getName()); | ||||||
|  |                         } catch (IllegalArgumentException e) { | ||||||
|  |                             log.warn("Bind was found but type {} is not supported", getAt().getUid().getType()); | ||||||
|  |                             return Optional.empty(); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     if (v.isPresent()) { | ||||||
|  |                         log.info("DN {} is the final match", entry.getDn().getName()); | ||||||
|  |                         return v; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (CursorLdapReferralException e) { | ||||||
|  |             log.warn("An entry is only available via referral, skipping"); | ||||||
|  |         } catch (IOException | LdapException | CursorException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Optional.empty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<_ThreePid> getThreepids(_MatrixID userId) { | ||||||
|  |         String uid = buildUidFromMatrixId(userId); | ||||||
|  |         log.info("Searching for 3PIDs of {}:", uid); | ||||||
|  |  | ||||||
|  |         List<_ThreePid> threePids = new ArrayList<>(); | ||||||
|  |         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); | ||||||
|  |  | ||||||
|  |                 String searchQuery = buildOrQueryWithFilter(getCfg().getProfile().getFilter(), uid, getUidAtt()); | ||||||
|  |  | ||||||
|  |                 log.debug("Query for 3PID {}: {}", medium, searchQuery); | ||||||
|  |  | ||||||
|  |                 try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) { | ||||||
|  |                     while (cursor.next()) { | ||||||
|  |                         Entry entry = cursor.get(); | ||||||
|  |                         log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||||
|  |                         try { | ||||||
|  |                             attributes.stream() | ||||||
|  |                                     .flatMap(at -> getAttributes(entry, at).stream()) | ||||||
|  |                                     .forEach(address -> { | ||||||
|  |                                         log.info("Found 3PID: {} - {}", medium, address); | ||||||
|  |                                         threePids.add(new ThreePid(medium, address)); | ||||||
|  |                                     }); | ||||||
|  |                         } catch (IllegalArgumentException e) { | ||||||
|  |                             log.warn("Bind was found but type {} is not supported", getAt().getUid().getType()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (CursorLdapReferralException e) { | ||||||
|  |                     log.warn("An entry is only available via referral, skipping"); | ||||||
|  |                 } catch (IOException | LdapException | CursorException e) { | ||||||
|  |                     throw new InternalServerError(e); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         } catch (IOException | LdapException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return threePids; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<String> getRoles(_MatrixID userId) { | ||||||
|  |         return Collections.emptyList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,145 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.ldap; | ||||||
|  |  | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.LdapConfig; | ||||||
|  | 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 io.kamax.mxisd.util.GsonUtil; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; | ||||||
|  | import org.apache.directory.api.ldap.model.cursor.EntryCursor; | ||||||
|  | import org.apache.directory.api.ldap.model.entry.Entry; | ||||||
|  | import org.apache.directory.api.ldap.model.exception.LdapException; | ||||||
|  | import org.apache.directory.api.ldap.model.message.SearchScope; | ||||||
|  | import org.apache.directory.ldap.client.api.LdapConnection; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class); | ||||||
|  |  | ||||||
|  |     public LdapThreePidProvider(LdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return getCfg().isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isLocal() { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getPriority() { | ||||||
|  |         return 20; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Optional<String> lookup(LdapConnection conn, String medium, String value) { | ||||||
|  |         Optional<String> tPidQueryOpt = getCfg().getIdentity().getQuery(medium); | ||||||
|  |         if (!tPidQueryOpt.isPresent()) { | ||||||
|  |             log.warn("{} is not a configured 3PID type for LDAP lookup", medium); | ||||||
|  |             return Optional.empty(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 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())) { | ||||||
|  |             while (cursor.next()) { | ||||||
|  |                 Entry entry = cursor.get(); | ||||||
|  |                 log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||||
|  |  | ||||||
|  |                 Optional<String> data = getAttribute(entry, getUidAtt()); | ||||||
|  |                 if (!data.isPresent()) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 log.info("DN {} is a valid match", entry.getDn().getName()); | ||||||
|  |                 return Optional.of(buildMatrixIdFromUid(data.get())); | ||||||
|  |             } | ||||||
|  |         } catch (CursorLdapReferralException e) { | ||||||
|  |             log.warn("3PID {} is only available via referral, skipping", value); | ||||||
|  |         } catch (IOException | LdapException | CursorException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Optional.empty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||||
|  |         log.info("Performing LDAP lookup {} of type {}", request.getThreePid(), request.getType()); | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             bind(conn); | ||||||
|  |             return lookup(conn, request.getType(), request.getThreePid()).map(id -> new SingleLookupReply(request, id)); | ||||||
|  |         } catch (LdapException | IOException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||||
|  |         log.info("Looking up {} mappings", mappings.size()); | ||||||
|  |         List<ThreePidMapping> mappingsFound = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             bind(conn); | ||||||
|  |  | ||||||
|  |             for (ThreePidMapping mapping : mappings) { | ||||||
|  |                 try { | ||||||
|  |                     lookup(conn, mapping.getMedium(), mapping.getValue()).ifPresent(id -> { | ||||||
|  |                         mapping.setMxid(id); | ||||||
|  |                         mappingsFound.add(mapping); | ||||||
|  |                     }); | ||||||
|  |                 } catch (IllegalArgumentException e) { | ||||||
|  |                     log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (LdapException | IOException e) { | ||||||
|  |             throw new InternalServerError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return mappingsFound; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2018 Kamax Sàrl | ||||||
|  |  * | ||||||
|  |  * 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.ldap.netiq; | ||||||
|  |  | ||||||
|  | import io.kamax.mxisd.backend.ldap.LdapAuthProvider; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class NetIqLdapAuthProvider extends LdapAuthProvider { | ||||||
|  |  | ||||||
|  |     public NetIqLdapAuthProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted | ||||||
|  |     @Override | ||||||
|  |     public String buildMatrixIdFromUid(String uid) { | ||||||
|  |         return super.buildMatrixIdFromUid(uid).toLowerCase(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2018 Kamax Sàrl | ||||||
|  |  * | ||||||
|  |  * 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.ldap.netiq; | ||||||
|  |  | ||||||
|  | import io.kamax.mxisd.backend.ldap.LdapDirectoryProvider; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class NetIqLdapDirectoryProvider extends LdapDirectoryProvider { | ||||||
|  |  | ||||||
|  |     public NetIqLdapDirectoryProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted | ||||||
|  |     @Override | ||||||
|  |     public String buildMatrixIdFromUid(String uid) { | ||||||
|  |         return super.buildMatrixIdFromUid(uid).toLowerCase(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | /* | ||||||
|  |  * 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.ldap.netiq; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.backend.ldap.LdapProfileProvider; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class NetIqLdapProfileProvider extends LdapProfileProvider { | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public NetIqLdapProfileProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted | ||||||
|  |     @Override | ||||||
|  |     public String buildMatrixIdFromUid(String uid) { | ||||||
|  |         return super.buildMatrixIdFromUid(uid).toLowerCase(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted | ||||||
|  |     @Override | ||||||
|  |     public String buildUidFromMatrixId(_MatrixID mxid) { | ||||||
|  |         return super.buildUidFromMatrixId(mxid).toLowerCase(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2018 Kamax Sàrl | ||||||
|  |  * | ||||||
|  |  * 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.ldap.netiq; | ||||||
|  |  | ||||||
|  | import io.kamax.mxisd.backend.ldap.LdapThreePidProvider; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class NetIqLdapThreePidProvider extends LdapThreePidProvider { | ||||||
|  |  | ||||||
|  |     public NetIqLdapThreePidProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg, mxCfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted | ||||||
|  |     @Override | ||||||
|  |     public String buildMatrixIdFromUid(String uid) { | ||||||
|  |         return super.buildMatrixIdFromUid(uid).toLowerCase(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,178 @@ | |||||||
|  | /* | ||||||
|  |  * 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.memory; | ||||||
|  |  | ||||||
|  | import io.kamax.matrix.MatrixID; | ||||||
|  | import io.kamax.matrix.ThreePid; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.matrix._ThreePid; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  | 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.memory.MemoryIdentityConfig; | ||||||
|  | import io.kamax.mxisd.config.memory.MemoryStoreConfig; | ||||||
|  | import io.kamax.mxisd.config.memory.MemoryThreePid; | ||||||
|  | import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; | ||||||
|  | import io.kamax.mxisd.directory.IDirectoryProvider; | ||||||
|  | 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 io.kamax.mxisd.profile.ProfileProvider; | ||||||
|  | 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.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.function.Function; | ||||||
|  | import java.util.function.Predicate; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class MemoryIdentityStore implements AuthenticatorProvider, IDirectoryProvider, IThreePidProvider, ProfileProvider { | ||||||
|  |  | ||||||
|  |     private final Logger logger = LoggerFactory.getLogger(MemoryIdentityStore.class); | ||||||
|  |  | ||||||
|  |     private final MatrixConfig mxCfg; | ||||||
|  |     private final MemoryStoreConfig cfg; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public MemoryIdentityStore(MatrixConfig mxCfg, MemoryStoreConfig cfg) { | ||||||
|  |         this.mxCfg = mxCfg; | ||||||
|  |         this.cfg = cfg; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Optional<MemoryIdentityConfig> findByUsername(String username) { | ||||||
|  |         return cfg.getIdentities().stream() | ||||||
|  |                 .filter(id -> StringUtils.equals(id.getUsername(), username)) | ||||||
|  |                 .findFirst(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return cfg.isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<String> getDisplayName(_MatrixID mxid) { | ||||||
|  |         return findByUsername(mxid.getLocalPart()).map(MemoryIdentityConfig::getDisplayName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private UserDirectorySearchResult search( | ||||||
|  |             Predicate<MemoryIdentityConfig> predicate, | ||||||
|  |             Function<MemoryIdentityConfig, UserDirectorySearchResult.Result> mapper | ||||||
|  |     ) { | ||||||
|  |         UserDirectorySearchResult search = new UserDirectorySearchResult(); | ||||||
|  |         cfg.getIdentities().stream().filter(predicate).map(mapper).forEach(search::addResult); | ||||||
|  |         return search; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchByDisplayName(String query) { | ||||||
|  |         return search( | ||||||
|  |                 entry -> StringUtils.containsIgnoreCase(entry.getUsername(), query), | ||||||
|  |                 entry -> { | ||||||
|  |                     UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result(); | ||||||
|  |                     result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId()); | ||||||
|  |                     result.setDisplayName(entry.getUsername()); | ||||||
|  |                     return result; | ||||||
|  |                 } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchBy3pid(String query) { | ||||||
|  |         return search( | ||||||
|  |                 entry -> entry.getThreepids().stream() | ||||||
|  |                         .anyMatch(tpid -> StringUtils.containsIgnoreCase(tpid.getAddress(), query)), | ||||||
|  |                 entry -> { | ||||||
|  |                     UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result(); | ||||||
|  |                     result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId()); | ||||||
|  |                     result.setDisplayName(entry.getUsername()); | ||||||
|  |                     return result; | ||||||
|  |                 } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<_ThreePid> getThreepids(_MatrixID mxid) { | ||||||
|  |         List<_ThreePid> l = new ArrayList<>(); | ||||||
|  |         findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getThreepids())); | ||||||
|  |         return l; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<String> getRoles(_MatrixID mxid) { | ||||||
|  |         List<String> l = new ArrayList<>(); | ||||||
|  |         findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getRoles())); | ||||||
|  |         return l; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isLocal() { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getPriority() { | ||||||
|  |         return Integer.MAX_VALUE; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||||
|  |         logger.info("Performing lookup {} of type {}", request.getThreePid(), request.getType()); | ||||||
|  |         ThreePid req = new ThreePid(request.getType(), request.getThreePid()); | ||||||
|  |         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.empty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||||
|  |         return Collections.emptyList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public BackendAuthResult authenticate(_MatrixID mxid, String password) { | ||||||
|  |         return findByUsername(mxid.getLocalPart()).map(id -> { | ||||||
|  |             if (!StringUtils.equals(id.getUsername(), mxid.getLocalPart())) { | ||||||
|  |                 return BackendAuthResult.failure(); | ||||||
|  |             } else { | ||||||
|  |                 BackendAuthResult result = new BackendAuthResult(); | ||||||
|  |                 id.getThreepids().forEach(tpid -> result.withThreePid(new ThreePid(tpid.getMedium(), tpid.getAddress()))); | ||||||
|  |                 result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), ""); | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  |         }).orElseGet(BackendAuthResult::failure); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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 java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | public class LookupBulkResponseJson { | ||||||
|  |  | ||||||
|  |     private List<LookupSingleResponseJson> lookup = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |     public List<LookupSingleResponseJson> getLookup() { | ||||||
|  |         return lookup; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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; | ||||||
|  |  | ||||||
|  | public class LookupSingleRequestJson { | ||||||
|  |  | ||||||
|  |     private String medium; | ||||||
|  |     private String address; | ||||||
|  |  | ||||||
|  |     public LookupSingleRequestJson(String medium, String address) { | ||||||
|  |         this.medium = medium; | ||||||
|  |         this.address = address; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getMedium() { | ||||||
|  |         return medium; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getAddress() { | ||||||
|  |         return address; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -18,27 +18,26 @@ | |||||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package io.kamax.mxisd.lookup | package io.kamax.mxisd.backend.rest; | ||||||
| 
 | 
 | ||||||
| class SingleLookupRequest extends ALookupRequest { | import io.kamax.mxisd.UserID; | ||||||
| 
 | 
 | ||||||
|     private String type | public class LookupSingleResponseJson { | ||||||
|     private String threePid |  | ||||||
| 
 | 
 | ||||||
|     String getType() { |     private String medium; | ||||||
|         return type |     private String address; | ||||||
|  |     private UserID id; | ||||||
|  | 
 | ||||||
|  |     public String getMedium() { | ||||||
|  |         return medium; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void setType(String type) { |     public String getAddress() { | ||||||
|         this.type = type |         return address; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     String getThreePid() { |     public UserID getId() { | ||||||
|         return threePid |         return id; | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void setThreePid(String threePid) { |  | ||||||
|         this.threePid = threePid |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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 io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.auth.provider.AuthenticatorProvider; | ||||||
|  | import io.kamax.mxisd.auth.provider.BackendAuthResult; | ||||||
|  | import io.kamax.mxisd.config.rest.RestBackendConfig; | ||||||
|  | import io.kamax.mxisd.util.RestClientUtils; | ||||||
|  | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
|  | import org.apache.http.client.methods.HttpUriRequest; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class RestAuthProvider extends RestProvider implements AuthenticatorProvider { | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public RestAuthProvider(RestBackendConfig cfg) { | ||||||
|  |         super(cfg); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return cfg.isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public BackendAuthResult authenticate(_MatrixID mxid, String password) { | ||||||
|  |         RestAuthRequestJson auth = new RestAuthRequestJson(); | ||||||
|  |         auth.setMxid(mxid.getId()); | ||||||
|  |         auth.setLocalpart(mxid.getLocalPart()); | ||||||
|  |         auth.setDomain(mxid.getDomain()); | ||||||
|  |         auth.setPassword(password); | ||||||
|  |  | ||||||
|  |         HttpUriRequest req = RestClientUtils.post(cfg.getEndpoints().getAuth(), gson, "auth", auth); | ||||||
|  |         try (CloseableHttpResponse res = client.execute(req)) { | ||||||
|  |             int status = res.getStatusLine().getStatusCode(); | ||||||
|  |             if (status < 200 || status >= 300) { | ||||||
|  |                 return BackendAuthResult.failure(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return parser.parse(res, "auth", BackendAuthResult.class); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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; | ||||||
|  |  | ||||||
|  | public class RestAuthRequestJson { | ||||||
|  |  | ||||||
|  |     private String mxid; | ||||||
|  |     private String localpart; | ||||||
|  |     private String domain; | ||||||
|  |     private String password; | ||||||
|  |  | ||||||
|  |     public String getMxid() { | ||||||
|  |         return mxid; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setMxid(String mxid) { | ||||||
|  |         this.mxid = mxid; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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 getPassword() { | ||||||
|  |         return password; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setPassword(String password) { | ||||||
|  |         this.password = password; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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 io.kamax.matrix.MatrixID; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.rest.RestBackendConfig; | ||||||
|  | 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 io.kamax.mxisd.exception.InternalServerError; | ||||||
|  | import io.kamax.mxisd.util.RestClientUtils; | ||||||
|  | import org.apache.commons.io.IOUtils; | ||||||
|  | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider { | ||||||
|  |  | ||||||
|  |     private MatrixConfig mxCfg; | ||||||
|  |  | ||||||
|  |     public RestDirectoryProvider(RestBackendConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg); | ||||||
|  |         this.mxCfg = mxCfg; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return cfg.isEnabled() && StringUtils.isNotBlank(cfg.getEndpoints().getDirectory()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private UserDirectorySearchResult search(String by, String query) { | ||||||
|  |         UserDirectorySearchRequest request = new UserDirectorySearchRequest(query); | ||||||
|  |         request.setBy(by); | ||||||
|  |         try (CloseableHttpResponse httpResponse = client.execute(RestClientUtils.post(cfg.getEndpoints().getDirectory(), request))) { | ||||||
|  |             int status = httpResponse.getStatusLine().getStatusCode(); | ||||||
|  |             if (status < 200 || status >= 300) { | ||||||
|  |                 throw new InternalServerError("REST backend: Error: " + IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             UserDirectorySearchResult response = parser.parse(httpResponse, UserDirectorySearchResult.class); | ||||||
|  |             for (UserDirectorySearchResult.Result result : response.getResults()) { | ||||||
|  |                 result.setUserId(new MatrixID(result.getUserId(), mxCfg.getDomain()).getId()); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return response; | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new InternalServerError("REST backend: I/O error: " + e.getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchByDisplayName(String query) { | ||||||
|  |         return search("name", query); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public UserDirectorySearchResult searchBy3pid(String query) { | ||||||
|  |         return search("threepid", query); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/main/java/io/kamax/mxisd/backend/rest/RestProvider.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/main/java/io/kamax/mxisd/backend/rest/RestProvider.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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.FieldNamingPolicy; | ||||||
|  | import com.google.gson.Gson; | ||||||
|  | import com.google.gson.GsonBuilder; | ||||||
|  | import io.kamax.mxisd.config.rest.RestBackendConfig; | ||||||
|  | import io.kamax.mxisd.util.GsonParser; | ||||||
|  | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
|  | import org.apache.http.impl.client.HttpClients; | ||||||
|  |  | ||||||
|  | public class RestProvider { | ||||||
|  |  | ||||||
|  |     protected RestBackendConfig cfg; | ||||||
|  |     protected Gson gson; | ||||||
|  |     protected GsonParser parser; | ||||||
|  |     protected CloseableHttpClient client; | ||||||
|  |  | ||||||
|  |     public RestProvider(RestBackendConfig cfg) { | ||||||
|  |         this.cfg = cfg; | ||||||
|  |  | ||||||
|  |         client = HttpClients.createDefault(); | ||||||
|  |         gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); | ||||||
|  |         parser = new GsonParser(gson); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,131 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Maxime Dor | ||||||
|  |  * | ||||||
|  |  * https://max.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 io.kamax.matrix.MatrixID; | ||||||
|  | import io.kamax.matrix._MatrixID; | ||||||
|  | import io.kamax.mxisd.UserID; | ||||||
|  | import io.kamax.mxisd.UserIdType; | ||||||
|  | import io.kamax.mxisd.config.MatrixConfig; | ||||||
|  | import io.kamax.mxisd.config.rest.RestBackendConfig; | ||||||
|  | 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 io.kamax.mxisd.util.RestClientUtils; | ||||||
|  | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
|  | import org.apache.http.client.methods.HttpUriRequest; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class RestThreePidProvider extends RestProvider implements IThreePidProvider { | ||||||
|  |  | ||||||
|  |     private Logger log = LoggerFactory.getLogger(RestThreePidProvider.class); | ||||||
|  |  | ||||||
|  |     private MatrixConfig mxCfg; // FIXME should be done in the lookup manager | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     public RestThreePidProvider(RestBackendConfig cfg, MatrixConfig mxCfg) { | ||||||
|  |         super(cfg); | ||||||
|  |         this.mxCfg = mxCfg; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 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()); | ||||||
|  |         } else { | ||||||
|  |             return new MatrixID(id.getValue()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return cfg.isEnabled(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean isLocal() { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int getPriority() { | ||||||
|  |         return 20; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO refactor common code | ||||||
|  |     @Override | ||||||
|  |     public Optional<SingleLookupReply> find(SingleLookupRequest request) { | ||||||
|  |         String endpoint = cfg.getEndpoints().getIdentity().getSingle(); | ||||||
|  |         HttpUriRequest req = RestClientUtils.post(endpoint, gson, "lookup", | ||||||
|  |                 new LookupSingleRequestJson(request.getType(), request.getThreePid())); | ||||||
|  |  | ||||||
|  |         try (CloseableHttpResponse res = client.execute(req)) { | ||||||
|  |             int status = res.getStatusLine().getStatusCode(); | ||||||
|  |             if (status < 200 || status >= 300) { | ||||||
|  |                 log.warn("REST endpoint {} answered with status {}, no binding found", endpoint, status); | ||||||
|  |                 return Optional.empty(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Optional<LookupSingleResponseJson> responseOpt = parser.parseOptional(res, "lookup", LookupSingleResponseJson.class); | ||||||
|  |             return responseOpt.map(lookupSingleResponseJson -> new SingleLookupReply(request, getMxId(lookupSingleResponseJson.getId()))); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO refactor common code | ||||||
|  |     @Override | ||||||
|  |     public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) { | ||||||
|  |         List<LookupSingleRequestJson> ioListRequest = mappings.stream() | ||||||
|  |                 .map(mapping -> new LookupSingleRequestJson(mapping.getMedium(), mapping.getValue())) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |  | ||||||
|  |         HttpUriRequest req = RestClientUtils.post( | ||||||
|  |                 cfg.getEndpoints().getIdentity().getBulk(), gson, "lookup", ioListRequest); | ||||||
|  |         try (CloseableHttpResponse res = client.execute(req)) { | ||||||
|  |             mappings = new ArrayList<>(); | ||||||
|  |  | ||||||
|  |             int status = res.getStatusLine().getStatusCode(); | ||||||
|  |             if (status < 200 || status >= 300) { | ||||||
|  |                 return mappings; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             LookupBulkResponseJson listIo = parser.parse(res, LookupBulkResponseJson.class); | ||||||
|  |             return listIo.getLookup().stream() | ||||||
|  |                     .map(io -> new ThreePidMapping(io.getMedium(), io.getAddress(), getMxId(io.getId()).getId())) | ||||||
|  |                     .collect(Collectors.toList()); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new RuntimeException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user