Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
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
|
||||||
|
180
README.md
180
README.md
@@ -5,20 +5,20 @@ mxisd - Federated Matrix Identity Server Daemon
|
|||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Why use mxisd](#why-use-mxisd)
|
- [Why use mxisd](#why-use-mxisd)
|
||||||
- [Quick start](#quick-start)
|
- [Getting Started](#getting-started)
|
||||||
- [Support](#support)
|
- [Support](#support)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [Contact](#contact)
|
- [Contact](#contact)
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with enhanced features.
|
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
|
||||||
|
|
||||||
It is specifically designed to connect to an Identity store (AD/Samba/LDAP, SQL Database, Web services/application, ...)
|
It is specifically designed to connect to an Identity store (AD/Samba/LDAP, SQL Database, Web services/application, ...)
|
||||||
and ease the integration of the Matrix ecosystem with an existing infrastructure, or to build a new one using lasting
|
and ease the integration of the Matrix ecosystem with an existing infrastructure, or to build a new one using lasting
|
||||||
tools.
|
tools.
|
||||||
|
|
||||||
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Thrid-party Identifiers) for the Homeserver and its
|
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 identify a user, like:
|
users. 3PIDs can be anything that identify a user, like:
|
||||||
- Full name
|
- Full name
|
||||||
- Email address
|
- Email address
|
||||||
@@ -29,8 +29,9 @@ users. 3PIDs can be anything that identify a user, like:
|
|||||||
- Facebook ID
|
- Facebook ID
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
mxisd is an enhanced Identity service, which implements the [Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html)
|
mxisd is an enhanced Identity service, which implements the
|
||||||
but also several other features that greatly enhance user experience within Matrix.
|
[Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html) but also several
|
||||||
|
[other features](#features) that greatly enhance user experience within Matrix.
|
||||||
|
|
||||||
mxisd is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built as a
|
mxisd is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built as a
|
||||||
single coherent product.
|
single coherent product.
|
||||||
@@ -64,114 +65,20 @@ currently **cannot be removed**
|
|||||||
- Users can directly find each other using whatever attribute is relevant within your Identity store
|
- Users can directly find each other using whatever attribute is relevant within your Identity store
|
||||||
- Federate your Identity lookups so you can discover others and/or others can discover you, all with extensive ACLs
|
- Federate your Identity lookups so you can discover others and/or others can discover you, all with extensive ACLs
|
||||||
|
|
||||||
# Quick Start
|
# Getting started
|
||||||
1. [Preparation](#preparation)
|
See the [dedicated document](docs/getting-started.md)
|
||||||
2. [Install](#install)
|
|
||||||
3. [Configure](#configure)
|
|
||||||
4. [Integrate](#integrate)
|
|
||||||
5. [Validate](#validate)
|
|
||||||
|
|
||||||
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 service.
|
|
||||||
This will be a good ground work for further integration with your existing Identity stores.
|
|
||||||
|
|
||||||
## Preparation
|
|
||||||
You will need:
|
|
||||||
- Homeserver
|
|
||||||
- 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.
|
|
||||||
You can also use a dedicated domain for mxisd, but will not have access to some features.
|
|
||||||
|
|
||||||
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/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](docs/architecture.md)
|
|
||||||
|
|
||||||
## Install
|
|
||||||
Install via:
|
|
||||||
- [Debian package](docs/install/debian.md)
|
|
||||||
- [Docker image](docs/install/docker.md)
|
|
||||||
- [Sources](docs/build.md)
|
|
||||||
|
|
||||||
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
|
|
||||||
|
|
||||||
## Configure
|
|
||||||
Create/edit a minimal configuration (see installer doc for the location):
|
|
||||||
```
|
|
||||||
matrix.domain: 'MyMatrixDomain.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
|
|
||||||
- `key.path` will store the signing keys, which must be kept safe!
|
|
||||||
- `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](docs/configure.md).
|
|
||||||
|
|
||||||
## Integrate
|
|
||||||
For an overview of a typical mxisd infrastructure, see the [dedicated document](docs/architecture.md)
|
|
||||||
### Reverse proxy
|
|
||||||
#### Apache2
|
|
||||||
In the VirtualHost handling the domain with SSL, add the following line and replace `0.0.0.0` by the right address/host.
|
|
||||||
**This line MUST be present before the one for the homeserver!**
|
|
||||||
```
|
|
||||||
ProxyPass /_matrix/identity http://0.0.0.0:8090/_matrix/identity
|
|
||||||
```
|
|
||||||
|
|
||||||
Typical VirtualHost configuration would be:
|
|
||||||
```
|
|
||||||
<VirtualHost *:443>
|
|
||||||
ServerName example.org
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
ProxyPreserveHost on
|
|
||||||
ProxyPass /_matrix/identity/ http://10.1.2.3:8090/_matrix/identity/
|
|
||||||
ProxyPass /_matrix/ http://10.1.2.3:8008/_matrix/
|
|
||||||
</VirtualHost>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 similair to:
|
|
||||||
```
|
|
||||||
trusted_third_party_id_servers:
|
|
||||||
- matrix.org
|
|
||||||
- vector.im
|
|
||||||
- example.org
|
|
||||||
```
|
|
||||||
It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is allowed by synapse.
|
|
||||||
|
|
||||||
### Federation and network discovery
|
|
||||||
See the [dedicated document](docs/features/federation.md).
|
|
||||||
|
|
||||||
## Validate
|
|
||||||
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.
|
|
||||||
Try to invite `mxisd-lookup-test@kamax.io`, which should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`
|
|
||||||
|
|
||||||
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](#support) and we'll do our best to get you started.
|
|
||||||
|
|
||||||
You can now integrate mxisd further with your infrastructure using the various [features](docs/README.md) guides.
|
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
## Community
|
## Community
|
||||||
If you need help, want to report a bug or just say hi, you can reach us on Matrix at
|
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) or [directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/).
|
[#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) or
|
||||||
|
[directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/).
|
||||||
For more high-level discussion about the Identity Server architecture/API, go to
|
For more high-level discussion about the Identity Server architecture/API, go to
|
||||||
[#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
|
[#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
|
||||||
|
|
||||||
## Professional
|
## Professional
|
||||||
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,
|
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open
|
||||||
please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
|
source technologies/products, please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
|
||||||
|
|
||||||
We offer affordable monthly/yearly support plans for mxisd, synapse or your full Matrix infrastructure.
|
We offer affordable monthly/yearly support plans for mxisd, synapse or your full Matrix infrastructure.
|
||||||
|
|
||||||
@@ -184,8 +91,8 @@ You can contribute as a community member by:
|
|||||||
- Helping us improve the documentation: tell us what is good or not good (in an issue or in Matrix), or make a PR with
|
- 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.
|
changes you feel improve the doc.
|
||||||
- Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3.
|
- Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3.
|
||||||
- Donate! any donation is welcome, regardless how small or big. This will directly be used for the fixed costs and
|
- [Donate!](https://liberapay.com/maximusdor/) Any donation is welcome, regardless how small or big, and will directly
|
||||||
developer time.
|
be used for the fixed costs and developer time of mxisd.
|
||||||
|
|
||||||
You can contribute as an organisation/corporation by:
|
You can contribute as an organisation/corporation by:
|
||||||
- Get a [support contract](#support-professional). This is the best way you can help us as it ensures mxisd is
|
- Get a [support contract](#support-professional). This is the best way you can help us as it ensures mxisd is
|
||||||
@@ -193,64 +100,7 @@ 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.
|
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
### Do I need to use mxisd if I run a Homeserver?
|
See the [dedicated document](docs/faq.md)
|
||||||
No, but it is recommended, even if you don't use any backends or integration.
|
|
||||||
|
|
||||||
mxisd in its default configuration will use federation and involve the central Matrix.org Identity servers when
|
|
||||||
performing queries, 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 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) only handles on specific flow:
|
|
||||||
validate credentials at login.
|
|
||||||
|
|
||||||
It does not:
|
|
||||||
- Auto-provision user profiles
|
|
||||||
- Integrate with Identity management
|
|
||||||
- Integrate with Directory searches
|
|
||||||
- Protect you against the username case sensitivites issues in synapse
|
|
||||||
|
|
||||||
mxisd is a replacement and enhancement of it, and also offers coherent results in all areas, which LDAP3 auth provider
|
|
||||||
does not.
|
|
||||||
|
|
||||||
### I saw that sydent is the official Identity server implemenation of the Matrix team, I should 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.
|
|
||||||
|
|
||||||
### 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 a misleading word currently.
|
|
||||||
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.
|
|
||||||
|
|
||||||
### So mxisd is just a big hack! I don't want to use non-official features!
|
|
||||||
mxisd primary concern is 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.
|
|
||||||
|
|
||||||
We also directly talk with the Matrix developers to ensure all features we implement have their approval, and that we
|
|
||||||
are in line with their vision of Identity management within the Matrix ecosystem.
|
|
||||||
|
|
||||||
Therefore, using mxisd is a safe choice. It will be like using the central Matrix.org Identity servers, yet not closing
|
|
||||||
the door to very nice enhancements and integrations.
|
|
||||||
|
|
||||||
### Should I use mxisd if I don't host my own Homeserver?
|
|
||||||
No
|
|
||||||
|
|
||||||
# Contact
|
# Contact
|
||||||
Get in touch via:
|
Get in touch via:
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
# Matrix config items #
|
# Matrix config items #
|
||||||
#######################
|
#######################
|
||||||
# Matrix domain, same as the domain configure in your Homeserver configuration.
|
# Matrix domain, same as the domain configure in your Homeserver configuration.
|
||||||
|
# (note: in Synapse Homeserver, the Matrix domain may be defined as 'server_name' in configuration file).
|
||||||
#
|
#
|
||||||
# This is used to build the various identifiers for identity, auth and directory.
|
# This is used to build the various identifiers for identity, auth and directory.
|
||||||
matrix.domain: ''
|
matrix.domain: ''
|
||||||
@@ -17,10 +18,12 @@ matrix.domain: ''
|
|||||||
# Absolute path for the Identity Server signing key.
|
# 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
|
||||||
|
#
|
||||||
|
# The signing key is auto-generated during execution time if not present.
|
||||||
key.path: ''
|
key.path: ''
|
||||||
|
|
||||||
|
|
||||||
@@ -92,5 +95,5 @@ threepid.medium.email.connectors.smtp.login: "matrix-identity@example.org"
|
|||||||
# Password for the account
|
# Password for the account
|
||||||
threepid.medium.email.connectors.smtp.password: "ThePassword"
|
threepid.medium.email.connectors.smtp.password: "ThePassword"
|
||||||
|
|
||||||
# The e-mail to send as. If empty, will be the same as login
|
# The e-mail to send as.
|
||||||
threepid.medium.email.identity.from: "matrix-identity@example.org"
|
threepid.medium.email.identity.from: "matrix-identity@example.org"
|
||||||
|
15
build.gradle
15
build.gradle
@@ -47,7 +47,7 @@ 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(), '')
|
||||||
@@ -66,6 +66,7 @@ buildscript {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url "https://kamax.io/maven/releases/" }
|
maven { url "https://kamax.io/maven/releases/" }
|
||||||
|
maven { url "https://kamax.io/maven/snapshots/" }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +75,13 @@ dependencies {
|
|||||||
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
|
// Thymeleaf for HTML templates
|
||||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.3.RELEASE"
|
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.RELEASE"
|
||||||
|
|
||||||
// Matrix Java SDK
|
// Matrix Java SDK
|
||||||
compile 'io.kamax:matrix-java-sdk:0.0.2'
|
compile 'io.kamax:matrix-java-sdk:0.0.11'
|
||||||
|
|
||||||
// ed25519 handling
|
// ed25519 handling
|
||||||
compile 'net.i2p.crypto:eddsa:0.1.0'
|
compile 'net.i2p.crypto:eddsa:0.1.0'
|
||||||
@@ -94,9 +95,6 @@ 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'
|
||||||
|
|
||||||
@@ -119,6 +117,9 @@ dependencies {
|
|||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
compile 'org.postgresql:postgresql:42.1.4'
|
compile 'org.postgresql:postgresql:42.1.4'
|
||||||
|
|
||||||
|
// MariaDB/MySQL
|
||||||
|
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
||||||
|
|
||||||
// Twilio SDK for SMS
|
// Twilio SDK for SMS
|
||||||
compile 'com.twilio.sdk:twilio:7.14.5'
|
compile 'com.twilio.sdk:twilio:7.14.5'
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
- [SQL](backends/sql.md)
|
- [SQL](backends/sql.md)
|
||||||
- [REST](backends/rest.md)
|
- [REST](backends/rest.md)
|
||||||
- [Google Firebase](backends/firebase.md)
|
- [Google Firebase](backends/firebase.md)
|
||||||
|
- [Wordpress](backends/wordpress.md)
|
||||||
- Notifications
|
- Notifications
|
||||||
- Handlers
|
- Handlers
|
||||||
- [Basic](threepids/notifications/basic-handler.md)
|
- [Basic](threepids/notifications/basic-handler.md)
|
||||||
|
1
docs/_config.yml
Normal file
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
@@ -1,6 +1,6 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
## Overview
|
## Overview
|
||||||
### Basic setup without integration or federation
|
### Basic setup without integration or incoming federation
|
||||||
```
|
```
|
||||||
Client
|
Client
|
||||||
|
|
|
|
||||||
|
6
docs/backends/README.md
Normal file
6
docs/backends/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Identity Stores (Backends)
|
||||||
|
- [Samba / Active Directory / LDAP](ldap.md)
|
||||||
|
- [SQL Databases](sql.md)
|
||||||
|
- [Website / Web service / Web app](rest.md)
|
||||||
|
- [Google Firebase](firebase.md)
|
||||||
|
- [Wordpress](wordpress.md)
|
@@ -1,4 +1,14 @@
|
|||||||
# Google Firebase
|
# Google Firebase
|
||||||
|
https://firebase.google.com/
|
||||||
|
|
||||||
|
## 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
|
## Configuration
|
||||||
To be completed. For now, see default structure and values:
|
To be completed. For now, see default structure and values:
|
||||||
```
|
```
|
||||||
|
@@ -1,53 +1,99 @@
|
|||||||
# AD/Samba/LDAP backend
|
# LDAP (Samba / Active Directory / OpenLDAP)
|
||||||
|
## Getting started
|
||||||
|
To use your LDAP backend, add the bare minimum configuration in mxisd config file:
|
||||||
|
```
|
||||||
|
ldap.enabled: true
|
||||||
|
ldap.connection.host: 'ldapHostnameOrIp'
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported):
|
||||||
|
```
|
||||||
|
ldap.connection.tls: true
|
||||||
|
ldap.connection.port: 12345
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can also set a default global filter on any LDAP queries:
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The following example would set the `uid` attribute as localpart and the Matrix display name to `cn`
|
||||||
|
```
|
||||||
|
ldap.attribute.uid.type: 'uid'
|
||||||
|
ldap.attribute.uid.value: 'uid'
|
||||||
|
ldap.attribute.name: 'cn'
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
```
|
||||||
|
ldap.attribute.threepid.email:
|
||||||
|
- 'mail'
|
||||||
|
- 'otherMailAttribute'
|
||||||
|
|
||||||
|
ldap.attribute.threepid.msisdn:
|
||||||
|
- 'phone'
|
||||||
|
- 'otherPhoneAttribute'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
If you would like to overwrite some global configuration relative to filter and/or attributes, see the Identity section
|
||||||
|
of the Configuration below.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
No further configuration is needed to enable authentication with LDAP once globally enabled and configured.
|
||||||
|
You have the possiblity to use a different query filter if you wish, see Configuration below.
|
||||||
|
|
||||||
|
Profile auto-fill is enabled by default. It will use the `name` and `threepid` configuration options to get a lit of
|
||||||
|
attributes to be used to build the user profile to pass on to synapse during authentication.
|
||||||
|
|
||||||
|
## Directory
|
||||||
|
No further configuration is needed to enable directory with LDAP once globally enabled and configured.
|
||||||
|
|
||||||
|
If you would like to use extra attributes in search that are not 3PIDs, like nicknames, group names, employee number:
|
||||||
|
```
|
||||||
|
ldap.directory.attribute.other:
|
||||||
|
- 'myNicknameAttribute'
|
||||||
|
- 'memberOf'
|
||||||
|
- 'employeeNumberAttribute'
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
### Structure and default values
|
Please read the [Configuration](../configure.md) explanatory note if you are not familiar with the terms used below.
|
||||||
```
|
|
||||||
ldap:
|
|
||||||
enabled: false
|
|
||||||
filter: ''
|
|
||||||
connection:
|
|
||||||
host: ''
|
|
||||||
tls: false
|
|
||||||
port: 389
|
|
||||||
bindDn: ''
|
|
||||||
bindPassword: ''
|
|
||||||
baseDn: ''
|
|
||||||
attribute:
|
|
||||||
uid:
|
|
||||||
type: 'uid'
|
|
||||||
value: 'userPrincipalName'
|
|
||||||
name: 'displayName'
|
|
||||||
threepid:
|
|
||||||
email:
|
|
||||||
- 'mailPrimaryAddress'
|
|
||||||
- 'mail'
|
|
||||||
- 'otherMailbox'
|
|
||||||
msisdn:
|
|
||||||
- 'telephoneNumber'
|
|
||||||
- 'mobile'
|
|
||||||
- 'homePhone'
|
|
||||||
- 'otherTelephone'
|
|
||||||
- 'otherMobile'
|
|
||||||
- 'otherHomePhone'
|
|
||||||
auth:
|
|
||||||
filter: ''
|
|
||||||
directory:
|
|
||||||
attribute:
|
|
||||||
other: []
|
|
||||||
filter: ''
|
|
||||||
identity:
|
|
||||||
filter: ''
|
|
||||||
medium:
|
|
||||||
email: ''
|
|
||||||
msisdn: ''
|
|
||||||
```
|
|
||||||
### General
|
### General
|
||||||
|
Base path: `ldap`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|-----------|-------------------------------------------------------------------------------------------|
|
|-----------|-------------------------------------------------------------------------------------------|
|
||||||
| `enabled` | Globaly enable/disable the LDAP backend |
|
| `enabled` | Globaly enable/disable the LDAP backend |
|
||||||
| `filter` | Global filter to apply on all LDAP queries. Can be overwritten in each applicable section |
|
| `filter` | Global filter to apply on all LDAP queries. Can be overwritten in each applicable section |
|
||||||
|
|
||||||
### Connection
|
### Connection
|
||||||
|
Base path: `ldap.connection`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|----------------|------------------------------------------------------|
|
|----------------|------------------------------------------------------|
|
||||||
| `host` | Host to connect to |
|
| `host` | Host to connect to |
|
||||||
@@ -58,6 +104,8 @@ ldap:
|
|||||||
| `baseDn` | Base DN for queries |
|
| `baseDn` | Base DN for queries |
|
||||||
|
|
||||||
### Attributes
|
### Attributes
|
||||||
|
Base path: `ldap.attribute`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|-------------|------------------------------------------------------------------------------------------------------------------------|
|
|-------------|------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `uid.type` | Indicate how to process the User ID (UID) attribute: |
|
| `uid.type` | Indicate how to process the User ID (UID) attribute: |
|
||||||
@@ -68,11 +116,15 @@ ldap:
|
|||||||
| `threepid` | Namespace where each key is a 3PID type and contains a list of attributes |
|
| `threepid` | Namespace where each key is a 3PID type and contains a list of attributes |
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
Base path: `ldap.auth`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|----------|--------------------------------------------------------------------------------------------------|
|
|----------|--------------------------------------------------------------------------------------------------|
|
||||||
| `filter` | Specific user filter applied during authentication. Global filter is used if empty/blank/not set |
|
| `filter` | Specific user filter applied during authentication. Global filter is used if empty/blank/not set |
|
||||||
|
|
||||||
### Directory
|
### Directory
|
||||||
|
Base path: `ldap.directory`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|-------------------|---------------------------------------------------------------------|
|
|-------------------|---------------------------------------------------------------------|
|
||||||
| `attribute.other` | Additional attributes to be used when performing directory searches |
|
| `attribute.other` | Additional attributes to be used when performing directory searches |
|
||||||
@@ -80,6 +132,8 @@ ldap:
|
|||||||
| | Global filter is used if empty/blank/not set |
|
| | Global filter is used if empty/blank/not set |
|
||||||
|
|
||||||
### Identity
|
### Identity
|
||||||
|
Base path: `ldap.identity`
|
||||||
|
|
||||||
| Item | Description |
|
| Item | Description |
|
||||||
|----------|---------------------------------------------------------------------------------------------------|
|
|----------|---------------------------------------------------------------------------------------------------|
|
||||||
| `filter` | Specific user filter applied during identity search. Global filter is used if empty/blank/not set |
|
| `filter` | Specific user filter applied during identity search. Global filter is used if empty/blank/not set |
|
||||||
|
55
docs/backends/wordpress.md
Normal file
55
docs/backends/wordpress.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Wordpress
|
||||||
|
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
|
||||||
|
|
||||||
|
This Identity store supports the following features:
|
||||||
|
- [Authentication](../features/authentication.md)
|
||||||
|
- [Directory](../features/directory-users.md)
|
||||||
|
- [Identity](../features/identity.md)
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
```
|
||||||
|
wordpress.enabled: true
|
||||||
|
```
|
||||||
|
Configure the URL to your Wordpress installation - see above about added `/index.php`:
|
||||||
|
```
|
||||||
|
wordpress.rest.base: 'http://localhost:8080'
|
||||||
|
```
|
||||||
|
Configure the SQL connection to your Wordpress database:
|
||||||
|
```
|
||||||
|
wordpress.sql.connection: '//127.0.0.1/wordpress?user=root&password=example'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
By default, MySQL database is expected. If you use another database, use:
|
||||||
|
```
|
||||||
|
wordpress.sql.type: 'jdbc-scheme'
|
||||||
|
```
|
||||||
|
With possible values:
|
||||||
|
- `mysql`
|
||||||
|
- `mariadb`
|
||||||
|
- `postgresql`
|
||||||
|
- `sqlite`
|
@@ -37,7 +37,7 @@ server:
|
|||||||
**WARNING:** mxisd might overwrite/adapt some values during launch. Those changes will not be reflected into copied keys.
|
**WARNING:** mxisd might overwrite/adapt some values during launch. Those changes will not be reflected into copied keys.
|
||||||
|
|
||||||
## Categories
|
## Categories
|
||||||
For each category below, the base configuration path will be given, which needs to be appened to every configuration
|
For each category below, the base configuration path will be given, which needs to be appended to every configuration
|
||||||
item described.
|
item described.
|
||||||
|
|
||||||
Example: if the base path was `basePath` and the following table was given:
|
Example: if the base path was `basePath` and the following table was given:
|
||||||
|
65
docs/faq.md
Normal file
65
docs/faq.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# FAQ
|
||||||
|
### Do I need to use mxisd if I run a Homeserver?
|
||||||
|
No, but it is recommended, even if you don't use any backends or integration.
|
||||||
|
|
||||||
|
In its default configuration, mxisd will talk to the central Matrix Identity servers and use other federated public
|
||||||
|
servers when performing queries, 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 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
|
||||||
|
- Protect you against the username case sensitivites issues in synapse
|
||||||
|
|
||||||
|
mxisd is a replacement and enhancement of it, offering coherent results in all areas, which 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?
|
||||||
|
In its default configuration, mxisd act as a proxy to Matrix.org/Vector.im. You will have access to the same data and
|
||||||
|
behaviour than if you were using them directly. There is no downside in using mxisd with the default configuration.
|
||||||
|
|
||||||
|
mxisd can also be configured not to talk to the central Identity servers if you wish.
|
||||||
|
|
||||||
|
### 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 a misleading word currently.
|
||||||
|
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.
|
||||||
|
|
||||||
|
### So mxisd is just a big hack! I don't want to use non-official features!
|
||||||
|
mxisd primary concern is 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.
|
||||||
|
|
||||||
|
We also directly talk with the Matrix developers to ensure all features we implement have their approval, and that we
|
||||||
|
are in line with their vision of Identity management within the Matrix ecosystem.
|
||||||
|
|
||||||
|
Therefore, using mxisd is a safe choice. It will be like using the central Matrix.org Identity servers, yet not closing
|
||||||
|
the door to very nice enhancements and integrations.
|
||||||
|
|
||||||
|
### Should I use mxisd if I don't host my own Homeserver?
|
||||||
|
No
|
@@ -1,8 +1,31 @@
|
|||||||
# Authentication
|
# Authentication
|
||||||
Performed via [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
|
|
||||||
Point the `endpoint` to mxisd internal IP on port 8090
|
- [Description](#description)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Getting started](#getting-started)
|
||||||
|
- [Synapse](#synapse)
|
||||||
|
- [mxisd](#mxisd)
|
||||||
|
- [Validate](#validate)
|
||||||
|
- [Next steps](#next-steps)
|
||||||
|
- [Profile auto-fil](#profile-auto-fill)
|
||||||
|
- [Advanced Authentication](#advanced-authentication)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
|
- [Apache2](#apache2)
|
||||||
|
- [DNS Overwrite](#dns-overwrite)
|
||||||
|
- [Backends](#backends)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Authentication is an enhanced Identity 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.
|
||||||
|
|
||||||
|
This feature can also provide the ability to users to login on the Homeserver using their third party identities (3PIDs) provided by an Identity store.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
An overview of the Authentication process is depicted below:
|
||||||
|
|
||||||
```
|
```
|
||||||
Backends
|
Backends
|
||||||
Client +------+
|
Client +------+
|
||||||
@@ -10,7 +33,7 @@ Point the `endpoint` to mxisd internal IP on port 8090
|
|||||||
| +---------------+ /_matrix/identity | mxisd | | +------+
|
| +---------------+ /_matrix/identity | mxisd | | +------+
|
||||||
+-> | Reverse proxy | >------------------+ | | |
|
+-> | Reverse proxy | >------------------+ | | |
|
||||||
+--|------------+ | | | | +--------+
|
+--|------------+ | | | | +--------+
|
||||||
| +-----> Check wiht backends >------+--> | SQL DB |
|
| +-----> Check with backends >------+--> | SQL DB |
|
||||||
Login request | | | | +--------+
|
Login request | | | | +--------+
|
||||||
| | | | | |
|
| | | | | |
|
||||||
| +--------------------------+ | +-----|-------------------+ +--> Others
|
| +--------------------------+ | +-----|-------------------+ +--> Others
|
||||||
@@ -23,6 +46,117 @@ Point the `endpoint` to mxisd internal IP on port 8090
|
|||||||
| user profiles | If valid credentials and supported by backend
|
| user profiles | If valid credentials and supported by backend
|
||||||
+--------------------------+
|
+--------------------------+
|
||||||
```
|
```
|
||||||
|
Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
|
||||||
|
|
||||||
## Profile auto-fill
|
## Getting started
|
||||||
To be documented
|
Authentication is possible by linking synapse and mxisd together using the REST auth module
|
||||||
|
(also known as password provider).
|
||||||
|
|
||||||
|
### Synapse
|
||||||
|
- Install the [REST auth module](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](../backends/)
|
||||||
|
- Restart mxisd
|
||||||
|
|
||||||
|
### Validate
|
||||||
|
Login on the Homeserver using credentials present in your backend.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
### Profile auto-fill
|
||||||
|
Auto-filling user profile depends on two conditions:
|
||||||
|
- The REST auth module is configured for it, which is the case by default
|
||||||
|
- Your Identity store is configured to provide profile data. See your Identity store [documentation](../backends/) on
|
||||||
|
how to enable the feature.
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced Authentication
|
||||||
|
The Authentication feature allows users to login to their Homeserver by using their 3PIDs registered in an available Identity store.
|
||||||
|
|
||||||
|
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. Enabled backends 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
|
||||||
|
- Reverse proxy setup
|
||||||
|
- Homeserver
|
||||||
|
- Compatible Identity backends:
|
||||||
|
- LDAP
|
||||||
|
- REST
|
||||||
|
- Wordpress
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### Reverse Proxy
|
||||||
|
|
||||||
|
##### Apache2
|
||||||
|
The specific configuration to put under the relevant `VirtualHost`:
|
||||||
|
```
|
||||||
|
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 like this:
|
||||||
|
```
|
||||||
|
<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 `application.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 /.
|
||||||
|
|
||||||
|
#### Backends
|
||||||
|
The Backends should be configured as described in the documentation of the [Directory User](directory-users.md) feature.
|
||||||
|
@@ -4,11 +4,14 @@
|
|||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Reverse Proxy](#reverse-proxy)
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
|
- [Apache2](#apache2)
|
||||||
|
- [nginx](#nginx)
|
||||||
- [DNS Overwrite](#dns-overwrite)
|
- [DNS Overwrite](#dns-overwrite)
|
||||||
- [Backends](#backends)
|
- [Backends](#backends)
|
||||||
- [LDAP](#ldap)
|
- [LDAP](#ldap)
|
||||||
- [SQL](#sql)
|
- [SQL](#sql)
|
||||||
- [REST](#rest)
|
- [REST](#rest)
|
||||||
|
- [Next steps](#next-steps)
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
|
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
|
||||||
@@ -61,16 +64,66 @@ which directly answered the request.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
### Reverse Proxy
|
### Reverse Proxy
|
||||||
Apache2 configuration to put under the relevant virtual domain:
|
#### Apache2
|
||||||
|
The specific configuration to put under the relevant `VirtualHost`:
|
||||||
```
|
```
|
||||||
ProxyPreserveHost on
|
ProxyPass /_matrix/client/r0/user_directory/ http://0.0.0.0:8090/_matrix/client/r0/user_directory/
|
||||||
ProxyPass /_matrix/identity/ http://mxisdInternalIpAddress:8090/_matrix/identity/
|
|
||||||
ProxyPass /_matrix/client/r0/user_directory/ http://mxisdInternalIpAddress:8090/_matrix/client/r0/user_directory/
|
|
||||||
ProxyPass /_matrix/ http://HomeserverInternalIpAddress:8008/_matrix/
|
|
||||||
```
|
```
|
||||||
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building
|
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building
|
||||||
results.
|
results.
|
||||||
|
|
||||||
|
Your `VirtualHost` should now look like this:
|
||||||
|
```
|
||||||
|
<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:
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
```
|
||||||
|
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
|
### DNS Overwrite
|
||||||
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with
|
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.
|
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
|
||||||
@@ -165,3 +218,14 @@ For each query, `type` can be used to tell mxisd how to process the ID column:
|
|||||||
|
|
||||||
#### REST
|
#### REST
|
||||||
See the [dedicated document](../backends/rest.md)
|
See the [dedicated document](../backends/rest.md)
|
||||||
|
|
||||||
|
#### Wordpress
|
||||||
|
See the [dedicated document](../backends/wordpress.md)
|
||||||
|
|
||||||
|
## 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 config file:
|
||||||
|
```
|
||||||
|
directory.exclude.homeserever: true
|
||||||
|
```
|
||||||
|
@@ -1,3 +1,18 @@
|
|||||||
To be documented.
|
# Matrix Identity Service
|
||||||
|
**WARNING**: This document is incomplete and can be missleading.
|
||||||
|
|
||||||
Implementation of the [Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html)
|
Implementation of the [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html).
|
||||||
|
|
||||||
|
## Invitation
|
||||||
|
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.
|
||||||
|
12
docs/features/profile.md
Normal file
12
docs/features/profile.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Profile enhancement
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Reverse proxy
|
||||||
|
|
||||||
|
#### Apache
|
||||||
|
```
|
||||||
|
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1"
|
||||||
|
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)/(.+)" "http://127.0.0.1:8008/_matrix/client/r0/profile/$1/$2"
|
||||||
|
|
||||||
|
```
|
146
docs/getting-started.md
Normal file
146
docs/getting-started.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# 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 service.
|
||||||
|
This will be a good ground work for further integration with your existing Identity stores.
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
You will need:
|
||||||
|
- Homeserver
|
||||||
|
- 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.
|
||||||
|
You can also use a dedicated domain for mxisd, but will not have access to some features.
|
||||||
|
|
||||||
|
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/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)
|
||||||
|
- [Docker image](install/docker.md)
|
||||||
|
- [Sources](build.md)
|
||||||
|
|
||||||
|
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
**NOTE**: please view the install instruction for your platform, as this step might be optional/handled for you.
|
||||||
|
|
||||||
|
Create/edit a minimal configuration (see installer doc for the location):
|
||||||
|
```
|
||||||
|
matrix.domain: 'MyMatrixDomain.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
|
||||||
|
- `key.path` will store the signing keys, which must be kept safe!
|
||||||
|
- `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!**
|
||||||
|
```
|
||||||
|
ProxyPass /_matrix/identity/ http://0.0.0.0:8090/_matrix/identity/
|
||||||
|
```
|
||||||
|
|
||||||
|
Typical configuration would look like:
|
||||||
|
```
|
||||||
|
<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!**
|
||||||
|
```
|
||||||
|
location /_matrix/identity {
|
||||||
|
proxy_pass http://0.0.0.0:8090/_matrix/identity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Typical configuration would look like:
|
||||||
|
```
|
||||||
|
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 similair to:
|
||||||
|
```
|
||||||
|
trusted_third_party_id_servers:
|
||||||
|
- matrix.org
|
||||||
|
- vector.im
|
||||||
|
- example.org
|
||||||
|
```
|
||||||
|
It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is authoritative for your HS.
|
||||||
|
|
||||||
|
## Validate
|
||||||
|
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.
|
||||||
|
Invite `mxisd-lookup-test@kamax.io` to a room, which should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`.
|
||||||
|
**NOTE:** you might not see a Matrix 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](#support) and we'll do our best to get you started.
|
||||||
|
|
||||||
|
You can now integrate mxisd further with your infrastructure using the various [features](README.md) guides.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
Once your mxisd server is up and running, here are the next steps to further enhance and integrate your installation:
|
||||||
|
|
||||||
|
Enable extra features:
|
||||||
|
- [Federation](features/federation.md)
|
||||||
|
- [Authenticate with synapse](features/authentication.md), profile auto-provisioning if you wish
|
||||||
|
- [Directory search](features/directory-users.md)
|
||||||
|
|
||||||
|
Use your Identity stores:
|
||||||
|
- [LDAP / Samba / Active directory](backends/ldap.md)
|
||||||
|
- [SQL Database](backends/sql.md)
|
||||||
|
- [Website / Web service / Web app](backends/rest.md)
|
||||||
|
- [Google Firebase](backends/firebase.md)
|
||||||
|
- [Wordpress](backends/wordpress.md)
|
@@ -5,10 +5,18 @@ Pull the latest stable image:
|
|||||||
docker pull kamax/mxisd
|
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
|
## Run
|
||||||
Run it (adapt volume paths to your host):
|
Use the following command after adapting to your needs:
|
||||||
|
- The `MATRIX_DOMAIN` environment variable to yours
|
||||||
|
- The volumes host paths
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --rm -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd
|
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/)
|
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)
|
||||||
|
@@ -276,14 +276,14 @@ session:
|
|||||||
**IMPORTANT**: When using local-only mode, you will also need to link mxisd to synapse if you want user searches and invites to work.
|
**IMPORTANT**: When using local-only mode, you will also need to link mxisd to synapse if you want user searches and invites to work.
|
||||||
To do so, add/edit the following configuration keys:
|
To do so, add/edit the following configuration keys:
|
||||||
```
|
```
|
||||||
sql:
|
synapseSql:
|
||||||
enabled: true
|
enabled: true
|
||||||
type: 'postgresql'
|
type: 'SET TO PROPER VALUE'
|
||||||
connection: ''
|
connection: 'SET TO PROPER VALUE'
|
||||||
```
|
```
|
||||||
- `sql.enabled` set to `true` to activate the SQL backend.
|
- `synapseSql.enabled` set to `true` to activate the SQL backend.
|
||||||
- `sql.type` can be set to `sqlite` or `postgresql`, depending on your synapse setup.
|
- `synapseSql.type` can be set to `sqlite` or `postgresql`, depending on your synapse setup.
|
||||||
- `sql.connection` use a JDBC format which is appened after the `jdbc:type:` connection URI.
|
- `synapseSql.connection` use a JDBC format which is appened after the `jdbc:type:` connection URI.
|
||||||
Example values for each type:
|
Example values for each type:
|
||||||
- `sqlite`: `/path/to/homeserver.db`
|
- `sqlite`: `/path/to/homeserver.db`
|
||||||
- `postgresql`: `//localhost/database?user=synapse&password=synapse`
|
- `postgresql`: `//localhost/database?user=synapse&password=synapse`
|
||||||
|
@@ -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
|
@@ -21,8 +21,9 @@
|
|||||||
package io.kamax.mxisd.auth;
|
package io.kamax.mxisd.auth;
|
||||||
|
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix._ThreePid;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
@@ -52,7 +53,7 @@ public class AuthManager {
|
|||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
||||||
|
|
||||||
public UserAuthResult authenticate(String id, String password) {
|
public UserAuthResult authenticate(String id, String password) {
|
||||||
_MatrixID mxid = new MatrixID(id);
|
_MatrixID mxid = MatrixID.asAcceptable(id);
|
||||||
for (AuthenticatorProvider provider : providers) {
|
for (AuthenticatorProvider provider : providers) {
|
||||||
if (!provider.isEnabled()) {
|
if (!provider.isEnabled()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -63,16 +64,16 @@ public class AuthManager {
|
|||||||
|
|
||||||
String mxId;
|
String mxId;
|
||||||
if (UserIdType.Localpart.is(result.getId().getType())) {
|
if (UserIdType.Localpart.is(result.getId().getType())) {
|
||||||
mxId = new MatrixID(result.getId().getValue(), mxCfg.getDomain()).getId();
|
mxId = MatrixID.from(result.getId().getValue(), mxCfg.getDomain()).acceptable().getId();
|
||||||
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
|
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
|
||||||
mxId = new MatrixID(result.getId().getValue()).getId();
|
mxId = MatrixID.asAcceptable(result.getId().getValue()).getId();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
|
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName());
|
UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName());
|
||||||
for (ThreePid pid : result.getProfile().getThreePids()) {
|
for (_ThreePid pid : result.getProfile().getThreePids()) {
|
||||||
authResult.withThreePid(pid.getMedium(), pid.getAddress());
|
authResult.withThreePid(pid.getMedium(), pid.getAddress());
|
||||||
}
|
}
|
||||||
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.auth;
|
package io.kamax.mxisd.auth;
|
||||||
|
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.auth.provider;
|
package io.kamax.mxisd.auth.provider;
|
||||||
|
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.UserID;
|
import io.kamax.mxisd.UserID;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
|
|
||||||
@@ -66,7 +66,6 @@ public class BackendAuthResult {
|
|||||||
public void succeed(String id, String type, String displayName) {
|
public void succeed(String id, String type, String displayName) {
|
||||||
this.success = true;
|
this.success = true;
|
||||||
this.id = new UserID(type, id);
|
this.id = new UserID(type, id);
|
||||||
this.profile = new BackendAuthProfile();
|
|
||||||
this.profile.displayName = displayName;
|
this.profile.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,9 +23,9 @@ package io.kamax.mxisd.backend.firebase;
|
|||||||
import com.google.firebase.auth.UserInfo;
|
import com.google.firebase.auth.UserInfo;
|
||||||
import com.google.i18n.phonenumbers.NumberParseException;
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.ThreePid;
|
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
|
@@ -20,12 +20,17 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.backend.ldap;
|
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.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.ldap.LdapConfig;
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.directory.api.ldap.model.cursor.CursorException;
|
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.CursorLdapReferralException;
|
||||||
@@ -41,12 +46,18 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class LdapAuthProvider extends LdapGenericBackend implements AuthenticatorProvider {
|
public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
||||||
|
|
||||||
|
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
super(cfg, mxCfg);
|
super(cfg, mxCfg);
|
||||||
@@ -57,6 +68,21 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
return getCfg().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
|
@Override
|
||||||
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
log.info("Performing auth for {}", mxid);
|
log.info("Performing auth for {}", mxid);
|
||||||
@@ -66,7 +92,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
bind(conn);
|
bind(conn);
|
||||||
|
|
||||||
String uidType = getAt().getUid().getType();
|
String uidType = getAt().getUid().getType();
|
||||||
String userFilterValue = StringUtils.equals(LdapGenericBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
|
String userFilterValue = StringUtils.equals(LdapBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
|
||||||
if (StringUtils.isBlank(userFilterValue)) {
|
if (StringUtils.isBlank(userFilterValue)) {
|
||||||
log.warn("Username is empty, failing auth");
|
log.warn("Username is empty, failing auth");
|
||||||
return BackendAuthResult.failure();
|
return BackendAuthResult.failure();
|
||||||
@@ -74,7 +100,19 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
|
|
||||||
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
|
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
|
||||||
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
|
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
|
||||||
try (EntryCursor cursor = conn.search(getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAtt(), getAt().getName())) {
|
|
||||||
|
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()) {
|
while (cursor.next()) {
|
||||||
Entry entry = cursor.get();
|
Entry entry = cursor.get();
|
||||||
String dn = entry.getDn().getName();
|
String dn = entry.getDn().getName();
|
||||||
@@ -99,7 +137,24 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
log.info("DN {} is a valid match", dn);
|
log.info("DN {} is a valid match", dn);
|
||||||
|
|
||||||
// TODO should we canonicalize the MXID?
|
// TODO should we canonicalize the MXID?
|
||||||
return BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
|
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) {
|
} catch (CursorLdapReferralException e) {
|
||||||
log.warn("Entity for {} is only available via referral, skipping", mxid);
|
log.warn("Entity for {} is only available via referral, skipping", mxid);
|
||||||
|
@@ -21,10 +21,10 @@
|
|||||||
package io.kamax.mxisd.backend.ldap;
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.ldap.LdapAttributeConfig;
|
|
||||||
import io.kamax.mxisd.config.ldap.LdapConfig;
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.directory.api.ldap.model.entry.Attribute;
|
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.entry.Entry;
|
||||||
import org.apache.directory.api.ldap.model.exception.LdapException;
|
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||||
import org.apache.directory.ldap.client.api.LdapConnection;
|
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||||
@@ -32,21 +32,24 @@ import org.apache.directory.ldap.client.api.LdapNetworkConnection;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public abstract class LdapGenericBackend {
|
public abstract class LdapBackend {
|
||||||
|
|
||||||
public static final String UID = "uid";
|
public static final String UID = "uid";
|
||||||
public static final String MATRIX_ID = "mxid";
|
public static final String MATRIX_ID = "mxid";
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapGenericBackend.class);
|
private Logger log = LoggerFactory.getLogger(LdapBackend.class);
|
||||||
|
|
||||||
private LdapConfig cfg;
|
private LdapConfig cfg;
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
public LdapGenericBackend(LdapConfig cfg, MatrixConfig mxCfg) {
|
public LdapBackend(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
}
|
}
|
||||||
@@ -56,10 +59,10 @@ public abstract class LdapGenericBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String getBaseDn() {
|
protected String getBaseDn() {
|
||||||
return cfg.getConn().getBaseDn();
|
return cfg.getConnection().getBaseDn();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LdapAttributeConfig getAt() {
|
protected LdapConfig.Attribute getAt() {
|
||||||
return cfg.getAttribute();
|
return cfg.getAttribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,14 +71,14 @@ public abstract class LdapGenericBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized LdapConnection getConn() throws LdapException {
|
protected synchronized LdapConnection getConn() throws LdapException {
|
||||||
return new LdapNetworkConnection(cfg.getConn().getHost(), cfg.getConn().getPort(), cfg.getConn().isTls());
|
return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void bind(LdapConnection conn) throws LdapException {
|
protected void bind(LdapConnection conn) throws LdapException {
|
||||||
if (StringUtils.isBlank(cfg.getConn().getBindDn()) && StringUtils.isBlank(cfg.getConn().getBindPassword())) {
|
if (StringUtils.isBlank(cfg.getConnection().getBindDn()) && StringUtils.isBlank(cfg.getConnection().getBindPassword())) {
|
||||||
conn.anonymousBind();
|
conn.anonymousBind();
|
||||||
} else {
|
} else {
|
||||||
conn.bind(cfg.getConn().getBindDn(), cfg.getConn().getBindPassword());
|
conn.bind(cfg.getConnection().getBindDn(), cfg.getConnection().getBindPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +127,6 @@ public abstract class LdapGenericBackend {
|
|||||||
public Optional<String> getAttribute(Entry entry, String attName) {
|
public Optional<String> getAttribute(Entry entry, String attName) {
|
||||||
Attribute attribute = entry.get(attName);
|
Attribute attribute = entry.get(attName);
|
||||||
if (attribute == null) {
|
if (attribute == null) {
|
||||||
log.info("DN {}: no attribute {}, skipping", entry.getDn(), attName);
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,4 +139,22 @@ public abstract class LdapGenericBackend {
|
|||||||
return Optional.of(value);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -21,11 +21,11 @@
|
|||||||
package io.kamax.mxisd.backend.ldap;
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.ldap.LdapAttributeConfig;
|
|
||||||
import io.kamax.mxisd.config.ldap.LdapConfig;
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
import io.kamax.mxisd.directory.IDirectoryProvider;
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
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.CursorException;
|
||||||
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
|
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.cursor.EntryCursor;
|
||||||
@@ -43,7 +43,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class LdapDirectoryProvider extends LdapGenericBackend implements IDirectoryProvider {
|
public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class);
|
private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class);
|
||||||
|
|
||||||
@@ -64,15 +64,18 @@ public class LdapDirectoryProvider extends LdapGenericBackend implements IDirect
|
|||||||
try (LdapConnection conn = getConn()) {
|
try (LdapConnection conn = getConn()) {
|
||||||
bind(conn);
|
bind(conn);
|
||||||
|
|
||||||
LdapAttributeConfig atCfg = getCfg().getAttribute();
|
LdapConfig.Attribute atCfg = getCfg().getAttribute();
|
||||||
|
|
||||||
attributes = new ArrayList<>(attributes);
|
attributes = new ArrayList<>(attributes);
|
||||||
attributes.add(getUidAtt());
|
attributes.add(getUidAtt());
|
||||||
String[] attArray = new String[attributes.size()];
|
String[] attArray = new String[attributes.size()];
|
||||||
attributes.toArray(attArray);
|
attributes.toArray(attArray);
|
||||||
|
|
||||||
String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray);
|
String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray);
|
||||||
|
|
||||||
|
log.debug("Base DN: {}", getBaseDn());
|
||||||
log.debug("Query: {}", searchQuery);
|
log.debug("Query: {}", searchQuery);
|
||||||
|
log.debug("Attributes: {}", GsonUtil.build().toJson(attArray));
|
||||||
|
|
||||||
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
|
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
Entry entry = cursor.get();
|
Entry entry = cursor.get();
|
||||||
|
@@ -27,6 +27,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
|
|||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
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.CursorException;
|
||||||
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
|
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.cursor.EntryCursor;
|
||||||
@@ -44,7 +45,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider {
|
public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class);
|
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class);
|
||||||
|
|
||||||
@@ -68,13 +69,20 @@ public class LdapThreePidProvider extends LdapGenericBackend implements IThreePi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> lookup(LdapConnection conn, String medium, String value) {
|
private Optional<String> lookup(LdapConnection conn, String medium, String value) {
|
||||||
Optional<String> queryOpt = getCfg().getIdentity().getQuery(medium);
|
Optional<String> tPidQueryOpt = getCfg().getIdentity().getQuery(medium);
|
||||||
if (!queryOpt.isPresent()) {
|
if (!tPidQueryOpt.isPresent()) {
|
||||||
log.warn("{} is not a configured 3PID type for LDAP lookup", medium);
|
log.warn("{} is not a configured 3PID type for LDAP lookup", medium);
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
String searchQuery = queryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value);
|
// 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())) {
|
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
Entry entry = cursor.get();
|
Entry entry = cursor.get();
|
||||||
|
@@ -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,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,173 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Maxime Dor
|
||||||
|
*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -31,10 +31,12 @@ import io.kamax.mxisd.util.RestClientUtils;
|
|||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Component
|
||||||
public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider {
|
public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
|
@@ -24,7 +24,7 @@ import io.kamax.matrix._MatrixID;
|
|||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -32,15 +32,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SqlAuthProvider implements AuthenticatorProvider {
|
public class GenericSqlAuthProvider implements AuthenticatorProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(SqlAuthProvider.class);
|
private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ServerConfig srvCfg;
|
private ServerConfig srvCfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SqlProviderConfig cfg;
|
private GenericSqlProviderConfig cfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
@@ -22,8 +22,8 @@ package io.kamax.mxisd.backend.sql;
|
|||||||
|
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlConfig;
|
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
import io.kamax.mxisd.directory.IDirectoryProvider;
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
@@ -39,16 +39,16 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result;
|
import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result;
|
||||||
|
|
||||||
public abstract class SqlDirectoryProvider implements IDirectoryProvider {
|
public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(SqlDirectoryProvider.class);
|
private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class);
|
||||||
|
|
||||||
protected SqlConfig cfg;
|
protected SqlConfig cfg;
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
private SqlConnectionPool pool;
|
private SqlConnectionPool pool;
|
||||||
|
|
||||||
public SqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) {
|
public GenericSqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.pool = new SqlConnectionPool(cfg);
|
this.pool = new SqlConnectionPool(cfg);
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
@@ -72,7 +72,7 @@ public abstract class SqlDirectoryProvider implements IDirectoryProvider {
|
|||||||
return Optional.of(item);
|
return Optional.of(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserDirectorySearchResult search(String searchTerm, SqlProviderConfig.Query query) {
|
public UserDirectorySearchResult search(String searchTerm, GenericSqlProviderConfig.Query query) {
|
||||||
try (Connection conn = pool.get()) {
|
try (Connection conn = pool.get()) {
|
||||||
log.info("Will execute query: {}", query.getValue());
|
log.info("Will execute query: {}", query.getValue());
|
||||||
try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) {
|
try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) {
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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.sql;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GenericSqlThreePidProvider extends SqlThreePidProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public GenericSqlThreePidProvider(GenericSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -21,38 +21,39 @@
|
|||||||
package io.kamax.mxisd.backend.sql;
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
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.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import io.kamax.mxisd.profile.ProfileProvider;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Component
|
public abstract class SqlThreePidProvider implements IThreePidProvider, ProfileProvider {
|
||||||
public class SqlThreePidProvider implements IThreePidProvider {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
|
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
|
||||||
|
|
||||||
private SqlProviderConfig cfg;
|
private SqlConfig cfg;
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
private SqlConnectionPool pool;
|
private SqlConnectionPool pool;
|
||||||
|
|
||||||
@Autowired
|
public SqlThreePidProvider(SqlConfig cfg, MatrixConfig mxCfg) {
|
||||||
public SqlThreePidProvider(SqlProviderConfig cfg, MatrixConfig mxCfg) {
|
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.pool = new SqlConnectionPool(cfg);
|
this.pool = new SqlConnectionPool(cfg);
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
@@ -113,4 +114,31 @@ public class SqlThreePidProvider implements IThreePidProvider {
|
|||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<_ThreePid> getThreepids(_MatrixID mxid) {
|
||||||
|
List<_ThreePid> threepids = new ArrayList<>();
|
||||||
|
|
||||||
|
String stmtSql = cfg.getProfile().getThreepid().getQuery();
|
||||||
|
try (Connection conn = pool.get()) {
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(stmtSql);
|
||||||
|
stmt.setString(1, mxid.getId());
|
||||||
|
|
||||||
|
ResultSet rSet = stmt.executeQuery();
|
||||||
|
while (rSet.next()) {
|
||||||
|
String medium = rSet.getString("medium");
|
||||||
|
String address = rSet.getString("address");
|
||||||
|
threepids.add(new ThreePid(medium, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
return threepids;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getRoles(_MatrixID mxid) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
package io.kamax.mxisd.backend.sql;
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
|
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -32,17 +32,15 @@ import java.sql.PreparedStatement;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SynapseSqliteDirectoryProvider extends SqlDirectoryProvider {
|
public class SynapseSqlDirectoryProvider extends GenericSqlDirectoryProvider {
|
||||||
|
|
||||||
private SynapseSqlProviderConfig cfg;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public SynapseSqliteDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
public SynapseSqlDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
super(cfg, mxCfg);
|
super(cfg, mxCfg);
|
||||||
|
|
||||||
if (StringUtils.equals("sqlite", cfg.getType())) {
|
if (StringUtils.equals("sqlite", cfg.getType())) {
|
||||||
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
|
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
|
||||||
SqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
||||||
queries.getName().setValue(
|
queries.getName().setValue(
|
||||||
"select " + userId + ", displayname from profiles p where displayname like ?");
|
"select " + userId + ", displayname from profiles p where displayname like ?");
|
||||||
queries.getThreepid().setValue(
|
queries.getThreepid().setValue(
|
||||||
@@ -51,7 +49,7 @@ public class SynapseSqliteDirectoryProvider extends SqlDirectoryProvider {
|
|||||||
"where t.address like ?");
|
"where t.address like ?");
|
||||||
} else if (StringUtils.equals("postgresql", cfg.getType())) {
|
} else if (StringUtils.equals("postgresql", cfg.getType())) {
|
||||||
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
|
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
|
||||||
SqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
||||||
queries.getName().setValue(
|
queries.getName().setValue(
|
||||||
"select " + userId + ", displayname from profiles p where displayname ilike ?");
|
"select " + userId + ", displayname from profiles p where displayname ilike ?");
|
||||||
queries.getThreepid().setValue(
|
queries.getThreepid().setValue(
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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.sql;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SynapseSqlThreePidProvider extends SqlThreePidProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SynapseSqlThreePidProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
public class WordpressAuthData {
|
||||||
|
|
||||||
|
public String token;
|
||||||
|
private String userEmail;
|
||||||
|
private String userNicename;
|
||||||
|
private String userDisplayName;
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserEmail() {
|
||||||
|
return userEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserEmail(String userEmail) {
|
||||||
|
this.userEmail = userEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserNicename() {
|
||||||
|
return userNicename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserNicename(String userNicename) {
|
||||||
|
this.userNicename = userNicename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserDisplayName() {
|
||||||
|
return userDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserDisplayName(String userDisplayName) {
|
||||||
|
this.userDisplayName = userDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
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 org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressAuthProvider implements AuthenticatorProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressAuthProvider.class);
|
||||||
|
|
||||||
|
private WordpressRestBackend wordpress;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressAuthProvider(WordpressRestBackend wordpress) {
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
|
try {
|
||||||
|
WordpressAuthData data = wordpress.authenticate(mxid.getLocalPart(), password);
|
||||||
|
BackendAuthResult result = new BackendAuthResult();
|
||||||
|
if (StringUtils.isNotBlank(data.getUserEmail())) {
|
||||||
|
result.withThreePid(new ThreePid("email", data.getUserEmail()));
|
||||||
|
}
|
||||||
|
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), data.getUserDisplayName());
|
||||||
|
return result;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("Authentication failed for {}: {}", mxid.getId(), e.getMessage());
|
||||||
|
return BackendAuthResult.failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressDirectoryProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressDirectoryProvider.class);
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private WordressSqlBackend wordpress;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressDirectoryProvider(WordpressConfig cfg, WordressSqlBackend wordpress, MatrixConfig mxCfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
|
||||||
|
for (int i = 1; i <= stmt.getParameterMetaData().getParameterCount(); i++) {
|
||||||
|
stmt.setString(i, "%" + searchTerm + "%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<UserDirectorySearchResult.Result> processRow(ResultSet rSet) throws SQLException {
|
||||||
|
UserDirectorySearchResult.Result item = new UserDirectorySearchResult.Result();
|
||||||
|
item.setUserId(rSet.getString(1));
|
||||||
|
item.setDisplayName(rSet.getString(2));
|
||||||
|
return Optional.of(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDirectorySearchResult search(String searchTerm, String query) {
|
||||||
|
try (Connection conn = wordpress.getConnection()) {
|
||||||
|
log.info("Will execute query: {}", query);
|
||||||
|
try (PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
|
setParameters(stmt, searchTerm);
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
||||||
|
result.setLimited(false);
|
||||||
|
|
||||||
|
while (rSet.next()) {
|
||||||
|
processRow(rSet).ifPresent(e -> {
|
||||||
|
try {
|
||||||
|
e.setUserId(MatrixID.from(e.getUserId(), mxCfg.getDomain()).valid().getId());
|
||||||
|
result.addResult(e);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("Ignoring result {} - Invalid characters for a Matrix ID", e.getUserId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String searchTerm) {
|
||||||
|
log.info("Searching users by display name using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String searchTerm) {
|
||||||
|
log.info("Searching users by 3PID using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("threepid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.matrix.json.InvalidJsonException;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressRestBackend {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressRestBackend.class);
|
||||||
|
private final String jsonPath = "/wp-json";
|
||||||
|
private final String jwtPath = "/jwt-auth/v1";
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
private String jsonEndpoint;
|
||||||
|
private String jwtEndpoint;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressRestBackend(WordpressConfig cfg, CloseableHttpClient client) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
if (!cfg.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonEndpoint = cfg.getRest().getBase() + jsonPath;
|
||||||
|
jwtEndpoint = jsonEndpoint + jwtPath;
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateConfig() {
|
||||||
|
log.info("Validating JWT auth endpoint");
|
||||||
|
try (CloseableHttpResponse res = client.execute(new HttpGet(jwtEndpoint))) {
|
||||||
|
int status = res.getStatusLine().getStatusCode();
|
||||||
|
if (status != 200) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Got status code {}", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String data = EntityUtils.toString(res.getEntity());
|
||||||
|
if (StringUtils.isBlank(data)) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Got no/empty body data");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject body = GsonUtil.parseObj(data);
|
||||||
|
if (!body.has("namespace")) {
|
||||||
|
log.warn("JWT auth endpoint check failed: invalid namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("JWT auth endpoint check succeeded");
|
||||||
|
} catch (InvalidJsonException e) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Invalid JSON response: {}", e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Could not read API endpoint: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WordpressAuthData authenticate(String username, String password) {
|
||||||
|
JsonObject body = new JsonObject();
|
||||||
|
body.addProperty("username", username);
|
||||||
|
body.addProperty("password", password);
|
||||||
|
HttpPost req = RestClientUtils.post(jwtEndpoint + "/token", body);
|
||||||
|
try (CloseableHttpResponse res = client.execute(req)) {
|
||||||
|
int status = res.getStatusLine().getStatusCode();
|
||||||
|
String bodyRes = EntityUtils.toString(res.getEntity());
|
||||||
|
if (status != 200) {
|
||||||
|
throw new IllegalArgumentException(bodyRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GsonUtil.get().fromJson(bodyRes, WordpressAuthData.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void authenticate() {
|
||||||
|
WordpressAuthData data = authenticate(
|
||||||
|
cfg.getRest().getCredential().getUsername(),
|
||||||
|
cfg.getRest().getCredential().getPassword());
|
||||||
|
log.info("Internal authentication: success, logged in as " + data.getUserNicename());
|
||||||
|
token = data.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CloseableHttpResponse runRequest(HttpRequestBase request) throws IOException {
|
||||||
|
request.setHeader("Authorization", "Bearer " + token);
|
||||||
|
return client.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloseableHttpResponse withAuthentication(HttpRequestBase request) throws IOException {
|
||||||
|
CloseableHttpResponse response = runRequest(request);
|
||||||
|
if (response.getStatusLine().getStatusCode() == 403) { //FIXME we should check the JWT expiration time
|
||||||
|
authenticate();
|
||||||
|
response = runRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
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 org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressThreePidProvider implements IThreePidProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressThreePidProvider.class);
|
||||||
|
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private WordressSqlBackend wordpress;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressThreePidProvider(MatrixConfig mxCfg, WordpressConfig cfg, WordressSqlBackend wordpress) {
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<_MatrixID> find(ThreePid tpid) {
|
||||||
|
String query = cfg.getSql().getQuery().getThreepid().get(tpid.getMedium());
|
||||||
|
if (Objects.isNull(query)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = wordpress.getConnection()) {
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(query);
|
||||||
|
stmt.setString(1, tpid.getAddress());
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
while (rSet.next()) {
|
||||||
|
String uid = rSet.getString("uid");
|
||||||
|
log.info("Found match: {}", uid);
|
||||||
|
try {
|
||||||
|
return Optional.of(MatrixID.from(uid, mxCfg.getDomain()).valid());
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("Ignoring match {} - Invalid characters for a Matrix ID", uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No valid match found in Wordpress");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||||
|
return find(new ThreePid(request.getType(), request.getThreePid())).map(mxid -> new SingleLookupReply(request, mxid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||||
|
for (ThreePidMapping tpidMap : mappings) {
|
||||||
|
find(new ThreePid(tpidMap.getMedium(), tpidMap.getValue())).ifPresent(mxid -> tpidMap.setMxid(mxid.getId()));
|
||||||
|
}
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.wordpress;
|
||||||
|
|
||||||
|
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordressSqlBackend {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(WordressSqlBackend.class);
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
|
||||||
|
private ComboPooledDataSource ds;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordressSqlBackend(WordpressConfig cfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
|
||||||
|
ds = new ComboPooledDataSource();
|
||||||
|
ds.setJdbcUrl("jdbc:" + cfg.getSql().getType() + ":" + cfg.getSql().getConnection());
|
||||||
|
ds.setMinPoolSize(1);
|
||||||
|
ds.setMaxPoolSize(10);
|
||||||
|
ds.setAcquireIncrement(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return ds.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -18,45 +18,42 @@
|
|||||||
* 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.config.ldap;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "ldap.attribute")
|
@ConfigurationProperties("directory")
|
||||||
public class LdapAttributeConfig {
|
public class DirectoryConfig {
|
||||||
|
|
||||||
private LdapAttributeUidConfig uid;
|
private final transient Logger log = LoggerFactory.getLogger(DnsOverwriteConfig.class);
|
||||||
private String name;
|
|
||||||
private Map<String, List<String>> threepid = new HashMap<>();
|
public static class Exclude {
|
||||||
|
|
||||||
|
private boolean homeserver;
|
||||||
|
|
||||||
|
public boolean getHomeserver() {
|
||||||
|
return homeserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exclude setHomeserver(boolean homeserver) {
|
||||||
|
this.homeserver = homeserver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public LdapAttributeUidConfig getUid() {
|
|
||||||
return uid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUid(LdapAttributeUidConfig uid) {
|
private Exclude exclude = new Exclude();
|
||||||
this.uid = uid;
|
|
||||||
|
public Exclude getExclude() {
|
||||||
|
return exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public void setExclude(Exclude exclude) {
|
||||||
return name;
|
this.exclude = exclude;
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, List<String>> getThreepid() {
|
|
||||||
return threepid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThreepid(Map<String, List<String>> threepid) {
|
|
||||||
this.threepid = threepid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
76
src/main/java/io/kamax/mxisd/config/InvitationConfig.java
Normal file
76
src/main/java/io/kamax/mxisd/config/InvitationConfig.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("invite")
|
||||||
|
public class InvitationConfig {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(InvitationConfig.class);
|
||||||
|
|
||||||
|
public static class Resolution {
|
||||||
|
|
||||||
|
private boolean recursive;
|
||||||
|
private long timer;
|
||||||
|
|
||||||
|
public boolean isRecursive() {
|
||||||
|
return recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecursive(boolean recursive) {
|
||||||
|
this.recursive = recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimer() {
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimer(long timer) {
|
||||||
|
this.timer = timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resolution resolution;
|
||||||
|
|
||||||
|
public Resolution getResolution() {
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResolution(Resolution resolution) {
|
||||||
|
this.resolution = resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void build() {
|
||||||
|
log.info("--- Invite config ---");
|
||||||
|
log.info("Resolution: {}", GsonUtil.build().toJson(resolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,30 +20,146 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.config.ldap;
|
package io.kamax.mxisd.config.ldap;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.mxisd.backend.ldap.LdapGenericBackend;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.backend.ldap.LdapBackend;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Configuration
|
public abstract class LdapConfig {
|
||||||
@ConfigurationProperties(prefix = "ldap")
|
|
||||||
public class LdapConfig {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapConfig.class);
|
public static class UID {
|
||||||
private static Gson gson = new Gson();
|
|
||||||
|
|
||||||
private boolean enabled;
|
private String type;
|
||||||
private String filter;
|
private String value;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Attribute {
|
||||||
|
|
||||||
|
private UID uid;
|
||||||
|
private String name;
|
||||||
|
private Map<String, List<String>> threepid = new HashMap<>();
|
||||||
|
|
||||||
|
public UID getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(UID uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getThreepid() {
|
||||||
|
return threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreepid(Map<String, List<String>> threepid) {
|
||||||
|
this.threepid = threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Auth {
|
||||||
|
|
||||||
|
private String filter;
|
||||||
|
|
||||||
|
public String getFilter() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilter(String filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Connection {
|
||||||
|
|
||||||
|
private boolean tls;
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
private String bindDn;
|
||||||
|
private String bindPassword;
|
||||||
|
private String baseDn;
|
||||||
|
|
||||||
|
public boolean isTls() {
|
||||||
|
return tls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTls(boolean tls) {
|
||||||
|
this.tls = tls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindDn() {
|
||||||
|
return bindDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBindDn(String bindDn) {
|
||||||
|
this.bindDn = bindDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindPassword() {
|
||||||
|
return bindPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBindPassword(String bindPassword) {
|
||||||
|
this.bindPassword = bindPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseDn() {
|
||||||
|
return baseDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseDn(String baseDn) {
|
||||||
|
this.baseDn = baseDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class Directory {
|
public static class Directory {
|
||||||
|
|
||||||
@@ -82,12 +198,55 @@ public class LdapConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
public static class Identity {
|
||||||
private LdapConnectionConfig conn;
|
|
||||||
private LdapAttributeConfig attribute;
|
private String filter;
|
||||||
private LdapAuthConfig auth;
|
private String token;
|
||||||
|
private Map<String, String> medium = new HashMap<>();
|
||||||
|
|
||||||
|
public String getFilter() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilter(String filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMedium() {
|
||||||
|
return medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getQuery(String key) {
|
||||||
|
return Optional.ofNullable(medium.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMedium(Map<String, String> medium) {
|
||||||
|
this.medium = medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(LdapConfig.class);
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
private String filter;
|
||||||
|
|
||||||
|
private Connection connection;
|
||||||
|
private Attribute attribute;
|
||||||
|
private Auth auth;
|
||||||
private Directory directory;
|
private Directory directory;
|
||||||
private LdapIdentityConfig identity;
|
private Identity identity;
|
||||||
|
|
||||||
|
protected abstract String getConfigName();
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -105,27 +264,27 @@ public class LdapConfig {
|
|||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LdapConnectionConfig getConn() {
|
public Connection getConnection() {
|
||||||
return conn;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConn(LdapConnectionConfig conn) {
|
public void setConnection(Connection conn) {
|
||||||
this.conn = conn;
|
this.connection = conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LdapAttributeConfig getAttribute() {
|
public Attribute getAttribute() {
|
||||||
return attribute;
|
return attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAttribute(LdapAttributeConfig attribute) {
|
public void setAttribute(Attribute attribute) {
|
||||||
this.attribute = attribute;
|
this.attribute = attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LdapAuthConfig getAuth() {
|
public Auth getAuth() {
|
||||||
return auth;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuth(LdapAuthConfig auth) {
|
public void setAuth(Auth auth) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,42 +296,45 @@ public class LdapConfig {
|
|||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LdapIdentityConfig getIdentity() {
|
public Identity getIdentity() {
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdentity(LdapIdentityConfig identity) {
|
public void setIdentity(Identity identity) {
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void build() {
|
public void build() {
|
||||||
log.info("--- LDAP Config ---");
|
log.info("--- " + getConfigName() + " Config ---");
|
||||||
log.info("Enabled: {}", isEnabled());
|
log.info("Enabled: {}", isEnabled());
|
||||||
|
|
||||||
if (!isEnabled()) {
|
if (!isEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(conn.getHost())) {
|
if (StringUtils.isBlank(connection.getHost())) {
|
||||||
throw new IllegalStateException("LDAP Host must be configured!");
|
throw new IllegalStateException("LDAP Host must be configured!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conn.getPort() < 1 || conn.getPort() > 65535) {
|
if (connection.getPort() < 1 || connection.getPort() > 65535) {
|
||||||
throw new IllegalStateException("LDAP port is not valid");
|
throw new IllegalStateException("LDAP port is not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(connection.getBaseDn())) {
|
||||||
|
throw new ConfigurationException("ldap.connection.baseDn");
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(attribute.getUid().getType())) {
|
if (StringUtils.isBlank(attribute.getUid().getType())) {
|
||||||
throw new IllegalStateException("Attribute UID Type cannot be empty");
|
throw new IllegalStateException("Attribute UID Type cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(attribute.getUid().getValue())) {
|
if (StringUtils.isBlank(attribute.getUid().getValue())) {
|
||||||
throw new IllegalStateException("Attribute UID value cannot be empty");
|
throw new IllegalStateException("Attribute UID value cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
String uidType = attribute.getUid().getType();
|
String uidType = attribute.getUid().getType();
|
||||||
if (!StringUtils.equals(LdapGenericBackend.UID, uidType) && !StringUtils.equals(LdapGenericBackend.MATRIX_ID, uidType)) {
|
if (!StringUtils.equals(LdapBackend.UID, uidType) && !StringUtils.equals(LdapBackend.MATRIX_ID, uidType)) {
|
||||||
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType);
|
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +346,9 @@ public class LdapConfig {
|
|||||||
attribute.getThreepid().forEach((k, v) -> {
|
attribute.getThreepid().forEach((k, v) -> {
|
||||||
if (StringUtils.isBlank(identity.getMedium().get(k))) {
|
if (StringUtils.isBlank(identity.getMedium().get(k))) {
|
||||||
if (ThreePidMedium.PhoneNumber.is(k)) {
|
if (ThreePidMedium.PhoneNumber.is(k)) {
|
||||||
identity.getMedium().put(k, LdapGenericBackend.buildOrQuery("+" + getIdentity().getToken(), v));
|
identity.getMedium().put(k, LdapBackend.buildOrQuery("+" + getIdentity().getToken(), v));
|
||||||
} else {
|
} else {
|
||||||
identity.getMedium().put(k, LdapGenericBackend.buildOrQuery(getIdentity().getToken(), v));
|
identity.getMedium().put(k, LdapBackend.buildOrQuery(getIdentity().getToken(), v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -195,15 +357,15 @@ public class LdapConfig {
|
|||||||
getDirectory().setFilter(StringUtils.defaultIfBlank(getDirectory().getFilter(), getFilter()));
|
getDirectory().setFilter(StringUtils.defaultIfBlank(getDirectory().getFilter(), getFilter()));
|
||||||
getIdentity().setFilter(StringUtils.defaultIfBlank(getIdentity().getFilter(), getFilter()));
|
getIdentity().setFilter(StringUtils.defaultIfBlank(getIdentity().getFilter(), getFilter()));
|
||||||
|
|
||||||
log.info("Host: {}", conn.getHost());
|
log.info("Host: {}", connection.getHost());
|
||||||
log.info("Port: {}", conn.getPort());
|
log.info("Port: {}", connection.getPort());
|
||||||
log.info("Bind DN: {}", conn.getBindDn());
|
log.info("Bind DN: {}", connection.getBindDn());
|
||||||
log.info("Base DN: {}", conn.getBaseDn());
|
log.info("Base DN: {}", connection.getBaseDn());
|
||||||
|
|
||||||
log.info("Attribute: {}", gson.toJson(attribute));
|
log.info("Attribute: {}", GsonUtil.get().toJson(attribute));
|
||||||
log.info("Auth: {}", gson.toJson(auth));
|
log.info("Auth: {}", GsonUtil.get().toJson(auth));
|
||||||
log.info("Directory: {}", gson.toJson(directory));
|
log.info("Directory: {}", GsonUtil.get().toJson(directory));
|
||||||
log.info("Identity: {}", gson.toJson(identity));
|
log.info("Identity: {}", GsonUtil.get().toJson(identity));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,85 +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.ldap;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "ldap.connection")
|
|
||||||
public class LdapConnectionConfig {
|
|
||||||
|
|
||||||
private boolean tls;
|
|
||||||
private String host;
|
|
||||||
private int port;
|
|
||||||
private String bindDn;
|
|
||||||
private String bindPassword;
|
|
||||||
private String baseDn;
|
|
||||||
|
|
||||||
public boolean isTls() {
|
|
||||||
return tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTls(boolean tls) {
|
|
||||||
this.tls = tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHost(String host) {
|
|
||||||
this.host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPort(int port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindDn() {
|
|
||||||
return bindDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBindDn(String bindDn) {
|
|
||||||
this.bindDn = bindDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindPassword() {
|
|
||||||
return bindPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBindPassword(String bindPassword) {
|
|
||||||
this.bindPassword = bindPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBaseDn() {
|
|
||||||
return baseDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBaseDn(String baseDn) {
|
|
||||||
this.baseDn = baseDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,66 +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.ldap;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "ldap.identity")
|
|
||||||
public class LdapIdentityConfig {
|
|
||||||
|
|
||||||
private String filter;
|
|
||||||
private String token;
|
|
||||||
private Map<String, String> medium = new HashMap<>();
|
|
||||||
|
|
||||||
public String getFilter() {
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilter(String filter) {
|
|
||||||
this.filter = filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken(String token) {
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> getMedium() {
|
|
||||||
return medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getQuery(String key) {
|
|
||||||
return Optional.ofNullable(medium.get(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMedium(Map<String, String> medium) {
|
|
||||||
this.medium = medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* mxisd - Matrix Identity Server Daemon
|
* mxisd - Matrix Identity Server Daemon
|
||||||
* Copyright (C) 2017 Maxime Dor
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@@ -18,32 +18,21 @@
|
|||||||
* 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.config.ldap;
|
package io.kamax.mxisd.config.ldap.generic;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "ldap.attribute.uid")
|
@ConfigurationProperties(prefix = "ldap")
|
||||||
public class LdapAttributeUidConfig {
|
@Primary
|
||||||
|
public class GenericLdapConfig extends LdapConfig {
|
||||||
|
|
||||||
private String type;
|
@Override
|
||||||
private String value;
|
protected String getConfigName() {
|
||||||
|
return "Generic LDAP";
|
||||||
public String getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* mxisd - Matrix Identity Server Daemon
|
* mxisd - Matrix Identity Server Daemon
|
||||||
* Copyright (C) 2017 Maxime Dor
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@@ -18,23 +18,19 @@
|
|||||||
* 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.config.ldap;
|
package io.kamax.mxisd.config.ldap.netiq;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "ldap.auth")
|
@ConfigurationProperties(prefix = "netiq")
|
||||||
public class LdapAuthConfig {
|
public class NetIqLdapConfig extends LdapConfig {
|
||||||
|
|
||||||
private String filter;
|
@Override
|
||||||
|
protected String getConfigName() {
|
||||||
public String getFilter() {
|
return "NetIQ eDirectory";
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilter(String filter) {
|
|
||||||
this.filter = filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.config.memory;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MemoryIdentityConfig {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private List<MemoryThreePid> threepids = new ArrayList<>();
|
||||||
|
private List<String> roles = new ArrayList<>();
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MemoryThreePid> getThreepids() {
|
||||||
|
return threepids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreepids(List<MemoryThreePid> threepids) {
|
||||||
|
this.threepids = threepids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(List<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.config.memory;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("memory")
|
||||||
|
public class MemoryStoreConfig {
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
private List<MemoryIdentityConfig> identities;
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MemoryIdentityConfig> getIdentities() {
|
||||||
|
return identities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentities(List<MemoryIdentityConfig> identities) {
|
||||||
|
this.identities = identities;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* mxisd - Matrix Identity Server Daemon
|
* mxisd - Matrix Identity Server Daemon
|
||||||
* Copyright (C) 2017 Maxime Dor
|
* Copyright (C) 2018 Maxime Dor
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@@ -18,52 +18,33 @@
|
|||||||
* 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.config.memory;
|
||||||
|
|
||||||
// FIXME this should be in matrix-java-sdk
|
import io.kamax.matrix._ThreePid;
|
||||||
public class ThreePid {
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MemoryThreePid implements _ThreePid {
|
||||||
|
|
||||||
private String medium;
|
private String medium;
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
public ThreePid(ThreePid tpid) {
|
@Override
|
||||||
this(tpid.getMedium(), tpid.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThreePid(String medium, String address) {
|
|
||||||
this.medium = medium;
|
|
||||||
this.address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMedium() {
|
public String getMedium() {
|
||||||
return medium;
|
return medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMedium(String medium) {
|
||||||
|
this.medium = medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setAddress(String address) {
|
||||||
public String toString() {
|
this.address = address;
|
||||||
return getMedium() + ":" + getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
ThreePid threePid = (ThreePid) o;
|
|
||||||
|
|
||||||
if (!medium.equals(threePid.medium)) return false;
|
|
||||||
return address.equals(threePid.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = medium.hashCode();
|
|
||||||
result = 31 * result + address.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -24,21 +24,14 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties("sql")
|
@ConfigurationProperties("sql")
|
||||||
@Primary
|
@Primary
|
||||||
public class SqlProviderConfig extends SqlConfig {
|
public class GenericSqlProviderConfig extends SqlConfig {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getProviderName() {
|
protected String getProviderName() {
|
||||||
return "Generic SQL";
|
return "Generic SQL";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void build() {
|
|
||||||
super.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@@ -4,6 +4,7 @@ import io.kamax.mxisd.util.GsonUtil;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -36,22 +37,22 @@ public abstract class SqlConfig {
|
|||||||
|
|
||||||
public static class Type {
|
public static class Type {
|
||||||
|
|
||||||
private SqlProviderConfig.Query name = new SqlProviderConfig.Query();
|
private Query name = new Query();
|
||||||
private SqlProviderConfig.Query threepid = new SqlProviderConfig.Query();
|
private Query threepid = new Query();
|
||||||
|
|
||||||
public SqlProviderConfig.Query getName() {
|
public Query getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setName(SqlProviderConfig.Query name) {
|
public void setName(Query name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqlProviderConfig.Query getThreepid() {
|
public Query getThreepid() {
|
||||||
return threepid;
|
return threepid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setThreepid(SqlProviderConfig.Query threepid) {
|
public void setThreepid(Query threepid) {
|
||||||
this.threepid = threepid;
|
this.threepid = threepid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ public abstract class SqlConfig {
|
|||||||
public static class Directory {
|
public static class Directory {
|
||||||
|
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
private SqlProviderConfig.Type query = new SqlProviderConfig.Type();
|
private Type query = new Type();
|
||||||
|
|
||||||
public Boolean isEnabled() {
|
public Boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -84,11 +85,11 @@ public abstract class SqlConfig {
|
|||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqlProviderConfig.Type getQuery() {
|
public Type getQuery() {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setQuery(SqlProviderConfig.Type query) {
|
public void setQuery(Type query) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +136,41 @@ public abstract class SqlConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ProfileThreepids {
|
||||||
|
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Profile {
|
||||||
|
|
||||||
|
private ProfileThreepids threepid = new ProfileThreepids();
|
||||||
|
|
||||||
|
public ProfileThreepids getThreepid() {
|
||||||
|
return threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreepid(ProfileThreepids threepid) {
|
||||||
|
this.threepid = threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String type;
|
private String type;
|
||||||
private String connection;
|
private String connection;
|
||||||
private SqlProviderConfig.Auth auth = new SqlProviderConfig.Auth();
|
private Auth auth = new Auth();
|
||||||
private SqlProviderConfig.Directory directory = new SqlProviderConfig.Directory();
|
private Directory directory = new Directory();
|
||||||
private SqlProviderConfig.Identity identity = new SqlProviderConfig.Identity();
|
private Identity identity = new Identity();
|
||||||
|
private Profile profile = new Profile();
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -166,35 +196,41 @@ public abstract class SqlConfig {
|
|||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqlProviderConfig.Auth getAuth() {
|
public Auth getAuth() {
|
||||||
return auth;
|
return auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuth(SqlProviderConfig.Auth auth) {
|
public void setAuth(Auth auth) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqlProviderConfig.Directory getDirectory() {
|
public Directory getDirectory() {
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDirectory(SqlProviderConfig.Directory directory) {
|
public void setDirectory(Directory directory) {
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SqlProviderConfig.Identity getIdentity() {
|
public Identity getIdentity() {
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdentity(SqlProviderConfig.Identity identity) {
|
public void setIdentity(Identity identity) {
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Profile getProfile() {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProfile(Profile profile) {
|
||||||
|
this.profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract String getProviderName();
|
protected abstract String getProviderName();
|
||||||
|
|
||||||
public void build() {
|
protected void doBuild() {
|
||||||
log.info("--- " + getProviderName() + " Provider config ---");
|
|
||||||
|
|
||||||
if (getAuth().isEnabled() == null) {
|
if (getAuth().isEnabled() == null) {
|
||||||
getAuth().setEnabled(isEnabled());
|
getAuth().setEnabled(isEnabled());
|
||||||
}
|
}
|
||||||
@@ -206,6 +242,13 @@ public abstract class SqlConfig {
|
|||||||
if (getIdentity().isEnabled() == null) {
|
if (getIdentity().isEnabled() == null) {
|
||||||
getIdentity().setEnabled(isEnabled());
|
getIdentity().setEnabled(isEnabled());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void build() {
|
||||||
|
log.info("--- " + getProviderName() + " Provider config ---");
|
||||||
|
|
||||||
|
doBuild();
|
||||||
|
|
||||||
log.info("Enabled: {}", isEnabled());
|
log.info("Enabled: {}", isEnabled());
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
@@ -214,7 +257,9 @@ public abstract class SqlConfig {
|
|||||||
log.info("Auth enabled: {}", getAuth().isEnabled());
|
log.info("Auth enabled: {}", getAuth().isEnabled());
|
||||||
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
|
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
|
||||||
log.info("Identity type: {}", getIdentity().getType());
|
log.info("Identity type: {}", getIdentity().getType());
|
||||||
|
log.info("3PID mapping query: {}", getIdentity().getQuery());
|
||||||
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
|
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
|
||||||
|
log.info("Profile 3PID query: {}", getProfile().getThreepid().getQuery());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd.config.sql.synapse;
|
package io.kamax.mxisd.config.sql.synapse;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.sql.SqlConfig;
|
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@@ -36,8 +37,23 @@ public class SynapseSqlProviderConfig extends SqlConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void build() {
|
public void doBuild() {
|
||||||
super.build();
|
super.doBuild();
|
||||||
|
// FIXME check that the DB is not the mxisd one
|
||||||
|
// See https://matrix.to/#/!NPRUEisLjcaMtHIzDr:kamax.io/$1509377583327omXkC:kamax.io
|
||||||
|
|
||||||
|
getAuth().setEnabled(false); // Synapse does the auth, we only act as a directory/identity service.
|
||||||
|
|
||||||
|
if (getDirectory().isEnabled()) {
|
||||||
|
//FIXME set default queries for name and threepid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getIdentity().isEnabled()) {
|
||||||
|
if (StringUtils.isBlank(getIdentity().getType())) {
|
||||||
|
getIdentity().setType("mxid");
|
||||||
|
getIdentity().setQuery("SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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.config.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("wordpress")
|
||||||
|
public class WordpressConfig {
|
||||||
|
|
||||||
|
public static class Credential {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Rest {
|
||||||
|
|
||||||
|
private Credential credential = new Credential();
|
||||||
|
private String base;
|
||||||
|
|
||||||
|
public String getBase() {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBase(String base) {
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Credential getCredential() {
|
||||||
|
return credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredential(Credential credential) {
|
||||||
|
this.credential = credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Query {
|
||||||
|
|
||||||
|
private Map<String, String> threepid;
|
||||||
|
private Map<String, String> directory;
|
||||||
|
|
||||||
|
public Map<String, String> getThreepid() {
|
||||||
|
return threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreepid(Map<String, String> threepid) {
|
||||||
|
this.threepid = threepid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getDirectory() {
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirectory(Map<String, String> directory) {
|
||||||
|
this.directory = directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Sql {
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private String connection;
|
||||||
|
private Query query;
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnection(String connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Query getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(Query query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
private Rest rest = new Rest();
|
||||||
|
private Sql sql = new Sql();
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rest getRest() {
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRest(Rest rest) {
|
||||||
|
this.rest = rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sql getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSql(Sql sql) {
|
||||||
|
this.sql = sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void build() {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(getRest().getBase())) {
|
||||||
|
throw new ConfigurationException("wordpress.rest.base");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -54,21 +54,40 @@ public class DefaultExceptionHandler {
|
|||||||
return gson.toJson(obj);
|
return gson.toJson(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(RemoteLoginException.class)
|
||||||
|
public String handle(HttpServletRequest request, HttpServletResponse response, RemoteLoginException e) {
|
||||||
|
if (e.getErrorBodyMsgResp() != null) {
|
||||||
|
response.setStatus(e.getStatus());
|
||||||
|
log.info("Request {} {} - Error {}: {}", request.getMethod(), request.getRequestURL(), e.getErrorCode(), e.getError());
|
||||||
|
return gson.toJson(e.getErrorBodyMsgResp());
|
||||||
|
}
|
||||||
|
return handleGeneric(request, response, e);
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(InternalServerError.class)
|
@ExceptionHandler(InternalServerError.class)
|
||||||
public String handle(HttpServletRequest req, InternalServerError e, HttpServletResponse response) {
|
public String handle(HttpServletRequest request, HttpServletResponse response, InternalServerError e) {
|
||||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||||
log.error("Reference #{} - {}", e.getReference(), e.getInternalReason());
|
log.error("Reference #{} - {}", e.getReference(), e.getInternalReason());
|
||||||
} else {
|
} else {
|
||||||
log.error("Reference #{}", e);
|
log.error("Reference #{}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleGeneric(req, e, response);
|
return handleGeneric(request, response, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MatrixException.class)
|
@ExceptionHandler(FeatureNotAvailable.class)
|
||||||
public String handleGeneric(HttpServletRequest req, MatrixException e, HttpServletResponse response) {
|
public String handle(HttpServletRequest request, HttpServletResponse response, FeatureNotAvailable e) {
|
||||||
|
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||||
|
log.error("Feature not available: {}", e.getInternalReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleGeneric(request, response, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpMatrixException.class)
|
||||||
|
public String handleGeneric(HttpServletRequest request, HttpServletResponse response, HttpMatrixException e) {
|
||||||
response.setStatus(e.getStatus());
|
response.setStatus(e.getStatus());
|
||||||
return handle(req, e.getErrorCode(), e.getError());
|
return handle(request, e.getErrorCode(), e.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
38
src/main/java/io/kamax/mxisd/controller/PingController.java
Normal file
38
src/main/java/io/kamax/mxisd/controller/PingController.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Maxime Dor
|
||||||
|
*
|
||||||
|
* 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.controller;
|
||||||
|
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin
|
||||||
|
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public class PingController {
|
||||||
|
|
||||||
|
@RequestMapping(value = "/_matrix/identity/api/v1")
|
||||||
|
public String ping() {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
src/main/java/io/kamax/mxisd/controller/ProxyController.java
Normal file
54
src/main/java/io/kamax/mxisd/controller/ProxyController.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.controller;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||||
|
import io.kamax.mxisd.util.OptionalUtil;
|
||||||
|
import org.thymeleaf.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class ProxyController {
|
||||||
|
|
||||||
|
private final static String headerName = "Authorization";
|
||||||
|
private final static String headerValuePrefix = "Bearer ";
|
||||||
|
private final static String parameterName = "access_token";
|
||||||
|
|
||||||
|
Optional<String> findAccessTokenInHeaders(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getHeader(headerName))
|
||||||
|
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
|
||||||
|
.map(header -> header.substring(headerValuePrefix.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<String> findAccessTokenInQuery(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getParameter(parameterName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> findAccessToken(HttpServletRequest request) {
|
||||||
|
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(request), () -> findAccessTokenInQuery(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken(HttpServletRequest request) {
|
||||||
|
return findAccessToken(request).orElseThrow(AccessTokenNotFoundException::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,15 +20,27 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.controller.auth.v1;
|
package io.kamax.mxisd.controller.auth.v1;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.*;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
|
import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
import io.kamax.mxisd.auth.AuthManager;
|
import io.kamax.mxisd.auth.AuthManager;
|
||||||
import io.kamax.mxisd.auth.UserAuthResult;
|
import io.kamax.mxisd.auth.UserAuthResult;
|
||||||
import io.kamax.mxisd.controller.auth.v1.io.CredentialsValidationResponse;
|
import io.kamax.mxisd.controller.auth.v1.io.CredentialsValidationResponse;
|
||||||
|
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||||
import io.kamax.mxisd.exception.JsonMemberNotFoundException;
|
import io.kamax.mxisd.exception.JsonMemberNotFoundException;
|
||||||
|
import io.kamax.mxisd.exception.RemoteLoginException;
|
||||||
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
import io.kamax.mxisd.util.GsonParser;
|
import io.kamax.mxisd.util.GsonParser;
|
||||||
import io.kamax.mxisd.util.GsonUtil;
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -39,13 +51,18 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
|
// TODO export into SDK
|
||||||
|
private static final String logV1Url = "/_matrix/client/r0/login";
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(AuthController.class);
|
private Logger log = LoggerFactory.getLogger(AuthController.class);
|
||||||
|
|
||||||
private Gson gson = GsonUtil.build();
|
private Gson gson = GsonUtil.build();
|
||||||
@@ -54,6 +71,23 @@ public class AuthController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AuthManager mgr;
|
private AuthManager mgr;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LookupStrategy strategy;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientDnsOverwrite dns;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
private String resolveProxyUrl(HttpServletRequest req) {
|
||||||
|
URI target = URI.create(req.getRequestURL().toString());
|
||||||
|
URIBuilder builder = dns.transform(target);
|
||||||
|
String urlToLogin = builder.toString();
|
||||||
|
log.info("Proxy resolution: {} to {}", target.toString(), urlToLogin);
|
||||||
|
return urlToLogin;
|
||||||
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST)
|
@RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST)
|
||||||
public String checkCredentials(HttpServletRequest req) {
|
public String checkCredentials(HttpServletRequest req) {
|
||||||
try {
|
try {
|
||||||
@@ -84,4 +118,112 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = logV1Url, method = RequestMethod.GET)
|
||||||
|
public String getLogin(HttpServletRequest req, HttpServletResponse res) {
|
||||||
|
try (CloseableHttpResponse hsResponse = client.execute(new HttpGet(resolveProxyUrl(req)))) {
|
||||||
|
res.setStatus(hsResponse.getStatusLine().getStatusCode());
|
||||||
|
return EntityUtils.toString(hsResponse.getEntity());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = logV1Url, method = RequestMethod.POST)
|
||||||
|
public String login(HttpServletRequest req) {
|
||||||
|
try {
|
||||||
|
JsonObject reqJsonObject = parser.parse(req.getInputStream());
|
||||||
|
|
||||||
|
// find 3PID in main object
|
||||||
|
GsonUtil.findPrimitive(reqJsonObject, "medium").ifPresent(medium -> {
|
||||||
|
GsonUtil.findPrimitive(reqJsonObject, "address").ifPresent(address -> {
|
||||||
|
log.info("Login request with medium '{}' and address '{}'", medium.getAsString(), address.getAsString());
|
||||||
|
strategy.findLocal(medium.getAsString(), address.getAsString()).ifPresent(lookupDataOpt -> {
|
||||||
|
reqJsonObject.addProperty("user", lookupDataOpt.getMxid().getLocalPart());
|
||||||
|
reqJsonObject.remove("medium");
|
||||||
|
reqJsonObject.remove("address");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// find 3PID in 'identifier' object
|
||||||
|
GsonUtil.findObj(reqJsonObject, "identifier").ifPresent(identifier -> {
|
||||||
|
GsonUtil.findPrimitive(identifier, "type").ifPresent(type -> {
|
||||||
|
|
||||||
|
if (StringUtils.equals(type.getAsString(), "m.id.thirdparty")) {
|
||||||
|
GsonUtil.findPrimitive(identifier, "medium").ifPresent(medium -> {
|
||||||
|
GsonUtil.findPrimitive(identifier, "address").ifPresent(address -> {
|
||||||
|
log.info("Login request with medium '{}' and address '{}'", medium.getAsString(), address.getAsString());
|
||||||
|
strategy.findLocal(medium.getAsString(), address.getAsString()).ifPresent(lookupDataOpt -> {
|
||||||
|
identifier.addProperty("type", "m.id.user");
|
||||||
|
identifier.addProperty("user", lookupDataOpt.getMxid().getLocalPart());
|
||||||
|
identifier.remove("medium");
|
||||||
|
identifier.remove("address");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.equals(type.getAsString(), "m.id.phone")) {
|
||||||
|
GsonUtil.findPrimitive(identifier, "number").ifPresent(number -> {
|
||||||
|
GsonUtil.findPrimitive(identifier, "country").ifPresent(country -> {
|
||||||
|
log.info("Login request with phone '{}'-'{}'", country.getAsString(), number.getAsString());
|
||||||
|
try {
|
||||||
|
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
|
Phonenumber.PhoneNumber phoneNumber = phoneUtil.parse(number.getAsString(), country.getAsString());
|
||||||
|
String canon_phoneNumber = phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164).replace("+", "");
|
||||||
|
String medium = "msisdn";
|
||||||
|
strategy.findLocal(medium, canon_phoneNumber).ifPresent(lookupDataOpt -> {
|
||||||
|
identifier.addProperty("type", "m.id.user");
|
||||||
|
identifier.addProperty("user", lookupDataOpt.getMxid().getLocalPart());
|
||||||
|
identifier.remove("country");
|
||||||
|
identifier.remove("number");
|
||||||
|
});
|
||||||
|
} catch (NumberParseException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// invoke 'login' on homeserver
|
||||||
|
HttpPost httpPost = RestClientUtils.post(resolveProxyUrl(req), gson, reqJsonObject);
|
||||||
|
try (CloseableHttpResponse httpResponse = client.execute(httpPost)) {
|
||||||
|
// check http status
|
||||||
|
int status = httpResponse.getStatusLine().getStatusCode();
|
||||||
|
log.info("http status = {}", status);
|
||||||
|
if (status != 200) {
|
||||||
|
// try to get possible json error message from response
|
||||||
|
// otherwise just get returned plain error message
|
||||||
|
String errcode = String.valueOf(httpResponse.getStatusLine().getStatusCode());
|
||||||
|
String error = EntityUtils.toString(httpResponse.getEntity());
|
||||||
|
if (httpResponse.getEntity() != null) {
|
||||||
|
try {
|
||||||
|
JsonObject bodyJson = new JsonParser().parse(error).getAsJsonObject();
|
||||||
|
if (bodyJson.has("errcode")) {
|
||||||
|
errcode = bodyJson.get("errcode").getAsString();
|
||||||
|
}
|
||||||
|
if (bodyJson.has("error")) {
|
||||||
|
error = bodyJson.get("error").getAsString();
|
||||||
|
}
|
||||||
|
throw new RemoteLoginException(status, errcode, error, bodyJson);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
log.warn("Response body is not JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RemoteLoginException(status, errcode, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return response
|
||||||
|
JsonObject respJsonObject = parser.parseOptional(httpResponse).get();
|
||||||
|
return gson.toJson(respJsonObject);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.controller.auth.v1.io;
|
package io.kamax.mxisd.controller.auth.v1.io;
|
||||||
|
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd.controller.directory.v1;
|
package io.kamax.mxisd.controller.directory.v1;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import io.kamax.mxisd.controller.ProxyController;
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
import io.kamax.mxisd.directory.DirectoryManager;
|
import io.kamax.mxisd.directory.DirectoryManager;
|
||||||
@@ -28,7 +29,10 @@ import io.kamax.mxisd.util.GsonParser;
|
|||||||
import io.kamax.mxisd.util.GsonUtil;
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -37,7 +41,7 @@ import java.net.URI;
|
|||||||
@RestController
|
@RestController
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@RequestMapping(path = "/_matrix/client/r0/user_directory", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
@RequestMapping(path = "/_matrix/client/r0/user_directory", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
public class UserDirectoryController {
|
public class UserDirectoryController extends ProxyController {
|
||||||
|
|
||||||
private Gson gson = GsonUtil.build();
|
private Gson gson = GsonUtil.build();
|
||||||
private GsonParser parser = new GsonParser(gson);
|
private GsonParser parser = new GsonParser(gson);
|
||||||
@@ -46,7 +50,8 @@ public class UserDirectoryController {
|
|||||||
private DirectoryManager mgr;
|
private DirectoryManager mgr;
|
||||||
|
|
||||||
@RequestMapping(path = "/search", method = RequestMethod.POST)
|
@RequestMapping(path = "/search", method = RequestMethod.POST)
|
||||||
public String search(HttpServletRequest request, @RequestParam("access_token") String accessToken) throws IOException {
|
public String search(HttpServletRequest request) throws IOException {
|
||||||
|
String accessToken = getAccessToken(request);
|
||||||
UserDirectorySearchRequest searchQuery = parser.parse(request, UserDirectorySearchRequest.class);
|
UserDirectorySearchRequest searchQuery = parser.parse(request, UserDirectorySearchRequest.class);
|
||||||
URI target = URI.create(request.getRequestURL().toString());
|
URI target = URI.create(request.getRequestURL().toString());
|
||||||
UserDirectorySearchResult result = mgr.search(target, accessToken, searchQuery.getSearchTerm());
|
UserDirectorySearchResult result = mgr.search(target, accessToken, searchQuery.getSearchTerm());
|
||||||
|
@@ -22,13 +22,13 @@ package io.kamax.mxisd.controller.identity.v1;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.crypto.KeyManager;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.controller.identity.v1.io.ThreePidInviteReplyIO;
|
import io.kamax.mxisd.controller.identity.v1.io.ThreePidInviteReplyIO;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInvite;
|
import io.kamax.mxisd.invitation.IThreePidInvite;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import io.kamax.mxisd.invitation.ThreePidInvite;
|
import io.kamax.mxisd.invitation.ThreePidInvite;
|
||||||
import io.kamax.mxisd.key.KeyManager;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@@ -22,9 +22,9 @@ package io.kamax.mxisd.controller.identity.v1;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.crypto.KeyManager;
|
||||||
import io.kamax.mxisd.controller.identity.v1.io.KeyValidityJson;
|
import io.kamax.mxisd.controller.identity.v1.io.KeyValidityJson;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.key.KeyManager;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -64,7 +64,7 @@ public class KeyController {
|
|||||||
|
|
||||||
@RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
|
@RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
|
||||||
public String checkEphemeralKeyValidity(HttpServletRequest request) {
|
public String checkEphemeralKeyValidity(HttpServletRequest request) {
|
||||||
log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid");
|
log.warn("Ephemeral key was requested but no ephemeral key are generated, replying not valid");
|
||||||
|
|
||||||
return invalidKey;
|
return invalidKey;
|
||||||
}
|
}
|
||||||
|
@@ -22,11 +22,14 @@ package io.kamax.mxisd.controller.identity.v1;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.crypto.SignatureManager;
|
||||||
|
import io.kamax.matrix.event.EventKey;
|
||||||
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.controller.identity.v1.io.SingeLookupReplyJson;
|
import io.kamax.mxisd.controller.identity.v1.io.SingeLookupReplyJson;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import io.kamax.mxisd.lookup.*;
|
import io.kamax.mxisd.lookup.*;
|
||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
import io.kamax.mxisd.signature.SignatureManager;
|
|
||||||
import io.kamax.mxisd.util.GsonParser;
|
import io.kamax.mxisd.util.GsonParser;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -57,6 +60,9 @@ public class MappingController {
|
|||||||
private Gson gson = new Gson();
|
private Gson gson = new Gson();
|
||||||
private GsonParser parser = new GsonParser(gson);
|
private GsonParser parser = new GsonParser(gson);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private LookupStrategy strategy;
|
private LookupStrategy strategy;
|
||||||
|
|
||||||
@@ -92,16 +98,12 @@ public class MappingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SingleLookupReply lookup = lookupOpt.get();
|
SingleLookupReply lookup = lookupOpt.get();
|
||||||
if (lookup.isSigned()) {
|
|
||||||
log.info("Lookup is already signed, sending as-is");
|
|
||||||
return lookup.getBody();
|
|
||||||
} else {
|
|
||||||
log.info("Lookup is not signed, signing");
|
|
||||||
JsonObject obj = gson.toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject();
|
|
||||||
obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj)));
|
|
||||||
|
|
||||||
return gson.toJson(obj);
|
// FIXME signing should be done in the business model, not in the controller
|
||||||
}
|
JsonObject obj = gson.toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject();
|
||||||
|
obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(MatrixJson.encodeCanonical(obj)));
|
||||||
|
|
||||||
|
return gson.toJson(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/bulk_lookup", method = POST)
|
@RequestMapping(value = "/bulk_lookup", method = POST)
|
||||||
|
@@ -23,7 +23,6 @@ package io.kamax.mxisd.controller.identity.v1;
|
|||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.config.ViewConfig;
|
import io.kamax.mxisd.config.ViewConfig;
|
||||||
import io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1;
|
import io.kamax.mxisd.controller.identity.v1.remote.RemoteIdentityAPIv1;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionMananger;
|
||||||
import io.kamax.mxisd.session.ValidationResult;
|
import io.kamax.mxisd.session.ValidationResult;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -36,7 +35,8 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||||
|
|
||||||
@@ -69,15 +69,15 @@ class SessionController {
|
|||||||
ValidationResult r = mgr.validate(sid, secret, token);
|
ValidationResult r = mgr.validate(sid, secret, token);
|
||||||
log.info("Session {} was validated", sid);
|
log.info("Session {} was validated", sid);
|
||||||
if (r.getNextUrl().isPresent()) {
|
if (r.getNextUrl().isPresent()) {
|
||||||
String url = srvCfg.getPublicUrl() + r.getNextUrl().get();
|
String url = r.getNextUrl().get();
|
||||||
log.info("Session {} validation: next URL is present, redirecting to {}", sid, url);
|
|
||||||
try {
|
try {
|
||||||
response.sendRedirect(url);
|
url = new URL(url).toString();
|
||||||
return "";
|
} catch (MalformedURLException e) {
|
||||||
} catch (IOException e) {
|
log.info("Session next URL {} is not a valid one, will prepend public URL {}", url, srvCfg.getPublicUrl());
|
||||||
log.warn("Unable to redirect user to {}", url);
|
url = srvCfg.getPublicUrl() + r.getNextUrl().get();
|
||||||
throw new InternalServerError(e);
|
|
||||||
}
|
}
|
||||||
|
log.info("Session {} validation: next URL is present, redirecting to {}", sid, url);
|
||||||
|
return "redirect:" + url;
|
||||||
} else {
|
} else {
|
||||||
if (r.isCanRemote()) {
|
if (r.isCanRemote()) {
|
||||||
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
|
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
|
||||||
|
@@ -22,8 +22,8 @@ package io.kamax.mxisd.controller.identity.v1;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.mxisd.ThreePid;
|
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.config.ViewConfig;
|
import io.kamax.mxisd.config.ViewConfig;
|
||||||
import io.kamax.mxisd.controller.identity.v1.io.SessionEmailTokenRequestJson;
|
import io.kamax.mxisd.controller.identity.v1.io.SessionEmailTokenRequestJson;
|
||||||
@@ -104,12 +104,19 @@ public class SessionRestController {
|
|||||||
|
|
||||||
if (ThreePidMedium.PhoneNumber.is(medium)) {
|
if (ThreePidMedium.PhoneNumber.is(medium)) {
|
||||||
SessionPhoneTokenRequestJson req = parser.parse(request, SessionPhoneTokenRequestJson.class);
|
SessionPhoneTokenRequestJson req = parser.parse(request, SessionPhoneTokenRequestJson.class);
|
||||||
return gson.toJson(new Sid(mgr.create(
|
ThreePid threepid = new ThreePid(req.getMedium(), req.getValue());
|
||||||
|
|
||||||
|
String sessionId = mgr.create(
|
||||||
request.getRemoteHost(),
|
request.getRemoteHost(),
|
||||||
new ThreePid(req.getMedium(), req.getValue()),
|
threepid,
|
||||||
req.getSecret(),
|
req.getSecret(),
|
||||||
req.getAttempt(),
|
req.getAttempt(),
|
||||||
req.getNextLink())));
|
req.getNextLink());
|
||||||
|
|
||||||
|
JsonObject res = new JsonObject();
|
||||||
|
res.addProperty("sid", sessionId);
|
||||||
|
res.addProperty(threepid.getMedium(), threepid.getAddress());
|
||||||
|
return gson.toJson(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject obj = new JsonObject();
|
JsonObject obj = new JsonObject();
|
||||||
|
@@ -22,9 +22,6 @@ package io.kamax.mxisd.controller.identity.v1.io;
|
|||||||
|
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class SingeLookupReplyJson {
|
public class SingeLookupReplyJson {
|
||||||
|
|
||||||
private String address;
|
private String address;
|
||||||
@@ -33,7 +30,6 @@ public class SingeLookupReplyJson {
|
|||||||
private long not_after;
|
private long not_after;
|
||||||
private long not_before;
|
private long not_before;
|
||||||
private long ts;
|
private long ts;
|
||||||
private Map<String, Map<String, String>> signatures = new HashMap<>();
|
|
||||||
|
|
||||||
public SingeLookupReplyJson(SingleLookupReply reply) {
|
public SingeLookupReplyJson(SingleLookupReply reply) {
|
||||||
this.address = reply.getRequest().getThreePid();
|
this.address = reply.getRequest().getThreePid();
|
||||||
@@ -68,8 +64,4 @@ public class SingeLookupReplyJson {
|
|||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return signatures != null && !signatures.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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.controller.profile.v1;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
|
import io.kamax.mxisd.controller.ProxyController;
|
||||||
|
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||||
|
import io.kamax.mxisd.profile.ProfileManager;
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
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 javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin
|
||||||
|
@RequestMapping(path = "/_matrix/client/r0/profile", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public class ProfileController extends ProxyController {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(ProfileController.class);
|
||||||
|
private final ProfileManager mgr;
|
||||||
|
private final CloseableHttpClient client;
|
||||||
|
private final ClientDnsOverwrite dns;
|
||||||
|
private final JsonParser parser;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ProfileController(ProfileManager mgr, CloseableHttpClient client, ClientDnsOverwrite dns) {
|
||||||
|
this.mgr = mgr;
|
||||||
|
this.client = client;
|
||||||
|
this.dns = dns;
|
||||||
|
this.parser = new JsonParser();
|
||||||
|
this.gson = GsonUtil.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME do properly in the SDK (headers, check access token, etc.)
|
||||||
|
private String resolveProxyUrl(HttpServletRequest req) {
|
||||||
|
URI target = URI.create(req.getRequestURL().toString() + (Objects.isNull(req.getQueryString()) ? "" : "?" + req.getQueryString()));
|
||||||
|
URIBuilder builder = dns.transform(target);
|
||||||
|
String urlToLogin = builder.toString();
|
||||||
|
log.info("Proxy resolution: {} to {}", target.toString(), urlToLogin);
|
||||||
|
return urlToLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/{userId:.+}")
|
||||||
|
public String getProfile(HttpServletRequest req, HttpServletResponse res, @PathVariable String userId) {
|
||||||
|
Optional<String> accessTokenOpt = findAccessToken(req);
|
||||||
|
HttpGet reqOut = new HttpGet(resolveProxyUrl(req));
|
||||||
|
accessTokenOpt.ifPresent(accessToken -> reqOut.addHeader("Authorization", "Bearer " + accessToken));
|
||||||
|
|
||||||
|
try (CloseableHttpResponse hsResponse = client.execute(reqOut)) {
|
||||||
|
res.setStatus(hsResponse.getStatusLine().getStatusCode());
|
||||||
|
JsonElement el = parser.parse(EntityUtils.toString(hsResponse.getEntity()));
|
||||||
|
List<_ThreePid> list = mgr.getThreepids(MatrixID.asAcceptable(userId));
|
||||||
|
if (!list.isEmpty() && el.isJsonObject()) {
|
||||||
|
JsonObject obj = el.getAsJsonObject();
|
||||||
|
obj.add("threepids", GsonUtil.build().toJsonTree(list));
|
||||||
|
}
|
||||||
|
return gson.toJson(el);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.controller.profile.v1;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.profile.ProfileManager;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@CrossOrigin
|
||||||
|
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||||
|
public class ProfileInternalController {
|
||||||
|
|
||||||
|
private final ProfileManager mgr;
|
||||||
|
|
||||||
|
public ProfileInternalController(ProfileManager mgr) {
|
||||||
|
this.mgr = mgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = GET, path = "/_matrix-internal/profile/v1/{userId:.+}")
|
||||||
|
public String getProfile(@PathVariable String userId) throws UnsupportedEncodingException {
|
||||||
|
userId = URLDecoder.decode(userId, StandardCharsets.UTF_8.name());
|
||||||
|
_MatrixID mxId = MatrixID.asAcceptable(userId);
|
||||||
|
|
||||||
|
return GsonUtil.get().toJson(GsonUtil.makeObj("roles", GsonUtil.asArray(mgr.getRoles(mxId))));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -23,20 +23,21 @@ package io.kamax.mxisd.directory;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import io.kamax.matrix.MatrixErrorInfo;
|
import io.kamax.matrix.MatrixErrorInfo;
|
||||||
|
import io.kamax.mxisd.config.DirectoryConfig;
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
|
||||||
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||||
|
import io.kamax.mxisd.exception.HttpMatrixException;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import io.kamax.mxisd.exception.MatrixException;
|
|
||||||
import io.kamax.mxisd.util.GsonUtil;
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
import io.kamax.mxisd.util.RestClientUtils;
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -53,16 +54,19 @@ public class DirectoryManager {
|
|||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(DirectoryManager.class);
|
private Logger log = LoggerFactory.getLogger(DirectoryManager.class);
|
||||||
|
|
||||||
|
private DirectoryConfig cfg;
|
||||||
private List<IDirectoryProvider> providers;
|
private List<IDirectoryProvider> providers;
|
||||||
|
|
||||||
private ClientDnsOverwrite dns;
|
private ClientDnsOverwrite dns;
|
||||||
private CloseableHttpClient client;
|
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DirectoryManager(List<IDirectoryProvider> providers, ClientDnsOverwrite dns) {
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DirectoryManager(DirectoryConfig cfg, List<IDirectoryProvider> providers, ClientDnsOverwrite dns) {
|
||||||
|
this.cfg = cfg;
|
||||||
this.dns = dns;
|
this.dns = dns;
|
||||||
this.client = HttpClients.custom().setUserAgent("mxisd").build(); //FIXME centralize
|
|
||||||
this.gson = GsonUtil.build();
|
this.gson = GsonUtil.build();
|
||||||
this.providers = providers.stream().filter(IDirectoryProvider::isEnabled).collect(Collectors.toList());
|
this.providers = providers.stream().filter(IDirectoryProvider::isEnabled).collect(Collectors.toList());
|
||||||
|
|
||||||
@@ -75,32 +79,41 @@ public class DirectoryManager {
|
|||||||
log.info("Original request URL: {}", target);
|
log.info("Original request URL: {}", target);
|
||||||
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
||||||
|
|
||||||
URIBuilder builder = dns.transform(target);
|
if (cfg.getExclude().getHomeserver()) {
|
||||||
log.info("Querying HS at {}", builder);
|
log.info("Skipping HS directory data, disabled in config");
|
||||||
builder.setParameter("access_token", accessToken);
|
} else {
|
||||||
HttpPost req = RestClientUtils.post(
|
URIBuilder builder = dns.transform(target);
|
||||||
builder.toString(),
|
log.info("Querying HS at {}", builder);
|
||||||
new UserDirectorySearchRequest(query));
|
builder.setParameter("access_token", accessToken);
|
||||||
try (CloseableHttpResponse res = client.execute(req)) {
|
HttpPost req = RestClientUtils.post(
|
||||||
int status = res.getStatusLine().getStatusCode();
|
builder.toString(),
|
||||||
Charset charset = ContentType.getOrDefault(res.getEntity()).getCharset();
|
new UserDirectorySearchRequest(query));
|
||||||
String body = IOUtils.toString(res.getEntity().getContent(), charset);
|
try (CloseableHttpResponse res = client.execute(req)) {
|
||||||
|
int status = res.getStatusLine().getStatusCode();
|
||||||
|
Charset charset = ContentType.getOrDefault(res.getEntity()).getCharset();
|
||||||
|
String body = IOUtils.toString(res.getEntity().getContent(), charset);
|
||||||
|
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
MatrixErrorInfo info = gson.fromJson(body, MatrixErrorInfo.class);
|
MatrixErrorInfo info = gson.fromJson(body, MatrixErrorInfo.class);
|
||||||
throw new MatrixException(status, info.getErrcode(), info.getError());
|
if (StringUtils.equals("M_UNRECOGNIZED", info.getErrcode())) { // FIXME no hardcoding, use Enum
|
||||||
}
|
log.warn("Homeserver does not support Directory feature, skipping");
|
||||||
|
} else {
|
||||||
|
log.error("Homeserver returned an error while performing directory search");
|
||||||
|
throw new HttpMatrixException(status, info.getErrcode(), info.getError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UserDirectorySearchResult resultHs = gson.fromJson(body, UserDirectorySearchResult.class);
|
UserDirectorySearchResult resultHs = gson.fromJson(body, UserDirectorySearchResult.class);
|
||||||
log.info("Found {} match(es) in HS for '{}'", resultHs.getResults().size(), query);
|
log.info("Found {} match(es) in HS for '{}'", resultHs.getResults().size(), query);
|
||||||
result.getResults().addAll(resultHs.getResults());
|
result.getResults().addAll(resultHs.getResults());
|
||||||
if (resultHs.isLimited()) {
|
if (resultHs.isLimited()) {
|
||||||
result.setLimited(true);
|
result.setLimited(true);
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
throw new InternalServerError("Invalid JSON reply from the HS: " + e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalServerError("Unable to query the HS: I/O error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (JsonSyntaxException e) {
|
|
||||||
throw new InternalServerError("Invalid JSON reply from the HS: " + e.getMessage());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalServerError("Unable to query the HS: I/O error: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IDirectoryProvider provider : providers) {
|
for (IDirectoryProvider provider : providers) {
|
||||||
|
@@ -22,6 +22,7 @@ package io.kamax.mxisd.dns;
|
|||||||
|
|
||||||
import io.kamax.mxisd.config.DnsOverwriteConfig;
|
import io.kamax.mxisd.config.DnsOverwriteConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -53,7 +54,7 @@ public class ClientDnsOverwrite {
|
|||||||
URIBuilder builder = new URIBuilder(initial);
|
URIBuilder builder = new URIBuilder(initial);
|
||||||
Entry mapping = mappings.get(initial.getHost());
|
Entry mapping = mappings.get(initial.getHost());
|
||||||
if (mapping == null) {
|
if (mapping == null) {
|
||||||
return builder;
|
throw new InternalServerError("No DNS client override for " + initial.getHost());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.exception;
|
||||||
|
|
||||||
|
public class AccessTokenNotFoundException extends HttpMatrixException {
|
||||||
|
|
||||||
|
public AccessTokenNotFoundException() {
|
||||||
|
super(401, "M_UNKNOWN_TOKEN", "An access token is required to access this resource");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.exception;
|
||||||
|
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
|
public class FeatureNotAvailable extends HttpMatrixException {
|
||||||
|
|
||||||
|
private String internalReason;
|
||||||
|
|
||||||
|
public FeatureNotAvailable(String internalReason) {
|
||||||
|
super(
|
||||||
|
HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||||
|
"M_NOT_AVAILABLE",
|
||||||
|
"This action is currently not available. Contact your administrator to enable it."
|
||||||
|
);
|
||||||
|
|
||||||
|
this.internalReason = internalReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInternalReason() {
|
||||||
|
return internalReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,28 +20,19 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.exception;
|
package io.kamax.mxisd.exception;
|
||||||
|
|
||||||
public class MatrixException extends MxisdException {
|
import io.kamax.matrix.MatrixException;
|
||||||
|
|
||||||
|
public class HttpMatrixException extends MatrixException {
|
||||||
|
|
||||||
private int status;
|
private int status;
|
||||||
private String errorCode;
|
|
||||||
private String error;
|
|
||||||
|
|
||||||
public MatrixException(int status, String errorCode, String error) {
|
public HttpMatrixException(int status, String errorCode, String error) {
|
||||||
|
super(errorCode, error);
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.errorCode = errorCode;
|
|
||||||
this.error = error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStatus() {
|
public int getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getErrorCode() {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@@ -24,7 +24,7 @@ import org.apache.http.HttpStatus;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public class InternalServerError extends MatrixException {
|
public class InternalServerError extends HttpMatrixException {
|
||||||
|
|
||||||
private String reference = Long.toString(Instant.now().toEpochMilli());
|
private String reference = Long.toString(Instant.now().toEpochMilli());
|
||||||
private String internalReason;
|
private String internalReason;
|
||||||
|
@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
|
|||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class MessageForClientException extends MatrixException {
|
public class MessageForClientException extends HttpMatrixException {
|
||||||
|
|
||||||
public MessageForClientException(String error) {
|
public MessageForClientException(String error) {
|
||||||
super(HttpStatus.SC_OK, "M_MESSAGE_FOR_CLIENT", error);
|
super(HttpStatus.SC_OK, "M_MESSAGE_FOR_CLIENT", error);
|
||||||
|
@@ -23,7 +23,7 @@ package io.kamax.mxisd.exception;
|
|||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class NotAllowedException extends MatrixException {
|
public class NotAllowedException extends HttpMatrixException {
|
||||||
|
|
||||||
public NotAllowedException(String s) {
|
public NotAllowedException(String s) {
|
||||||
super(HttpStatus.SC_FORBIDDEN, "M_FORBIDDEN", s);
|
super(HttpStatus.SC_FORBIDDEN, "M_FORBIDDEN", s);
|
||||||
|
@@ -2,7 +2,7 @@ package io.kamax.mxisd.exception;
|
|||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class RemoteHomeServerException extends MatrixException {
|
public class RemoteHomeServerException extends HttpMatrixException {
|
||||||
|
|
||||||
public RemoteHomeServerException(String error) {
|
public RemoteHomeServerException(String error) {
|
||||||
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_HS_ERROR", "Error from remote server: " + error);
|
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_HS_ERROR", "Error from remote server: " + error);
|
||||||
|
@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
|
|||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class RemoteIdentityServerException extends MatrixException {
|
public class RemoteIdentityServerException extends HttpMatrixException {
|
||||||
|
|
||||||
public RemoteIdentityServerException(String error) {
|
public RemoteIdentityServerException(String error) {
|
||||||
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_IS_ERROR", "Error from remote server: " + error);
|
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_IS_ERROR", "Error from remote server: " + error);
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.exception;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
public class RemoteLoginException extends HttpMatrixException {
|
||||||
|
|
||||||
|
private JsonObject errorBodyMsgResp;
|
||||||
|
|
||||||
|
public RemoteLoginException(int status, String errorCode, String error) {
|
||||||
|
super(status, errorCode, error);
|
||||||
|
this.errorBodyMsgResp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteLoginException(int status, String errorCode, String error, JsonObject errorBodyMsgResp) {
|
||||||
|
super(status, errorCode, error);
|
||||||
|
this.errorBodyMsgResp = errorBodyMsgResp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject getErrorBodyMsgResp() {
|
||||||
|
return errorBodyMsgResp;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
|
|||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class SessionNotValidatedException extends MatrixException {
|
public class SessionNotValidatedException extends HttpMatrixException {
|
||||||
|
|
||||||
public SessionNotValidatedException() {
|
public SessionNotValidatedException() {
|
||||||
super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.exception;
|
package io.kamax.mxisd.exception;
|
||||||
|
|
||||||
public class SessionUnknownException extends MatrixException {
|
public class SessionUnknownException extends HttpMatrixException {
|
||||||
|
|
||||||
public SessionUnknownException() {
|
public SessionUnknownException() {
|
||||||
this("No valid session was found matching that sid and client secret");
|
this("No valid session was found matching that sid and client secret");
|
||||||
|
@@ -24,6 +24,8 @@ import com.google.gson.Gson;
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.crypto.SignatureManager;
|
||||||
|
import io.kamax.mxisd.config.InvitationConfig;
|
||||||
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||||
@@ -31,7 +33,6 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
|
|||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
import io.kamax.mxisd.notification.NotificationManager;
|
import io.kamax.mxisd.notification.NotificationManager;
|
||||||
import io.kamax.mxisd.signature.SignatureManager;
|
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@@ -72,6 +73,9 @@ public class InvitationManager {
|
|||||||
|
|
||||||
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private InvitationConfig cfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStorage storage;
|
private IStorage storage;
|
||||||
|
|
||||||
@@ -137,7 +141,7 @@ public class InvitationManager {
|
|||||||
log.error("Error when running background mapping refresh", t);
|
log.error("Error when running background mapping refresh", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 5000L, TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)); // FIXME make configurable
|
}, 5000L, TimeUnit.MILLISECONDS.convert(cfg.getResolution().getTimer(), TimeUnit.MINUTES));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
@@ -204,6 +208,14 @@ public class InvitationManager {
|
|||||||
return "https://" + domain + ":8448";
|
return "https://" + domain + ":8448";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<SingleLookupReply> lookup3pid(String medium, String address) {
|
||||||
|
if (!cfg.getResolution().isRecursive()) {
|
||||||
|
log.warn("/!\\ /!\\ --- RECURSIVE INVITE RESOLUTION HAS BEEN DISABLED --- /!\\ /!\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookupMgr.find(medium, address, cfg.getResolution().isRecursive());
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
|
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
|
||||||
if (!notifMgr.isMediumSupported(invitation.getMedium())) {
|
if (!notifMgr.isMediumSupported(invitation.getMedium())) {
|
||||||
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
|
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
|
||||||
@@ -223,7 +235,7 @@ public class InvitationManager {
|
|||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true);
|
Optional<SingleLookupReply> result = lookup3pid(invitation.getMedium(), invitation.getAddress());
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress());
|
log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress());
|
||||||
throw new MappingAlreadyExistsException();
|
throw new MappingAlreadyExistsException();
|
||||||
@@ -333,7 +345,7 @@ public class InvitationManager {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
log.info("Searching for mapping created since invite {} was created", getIdForLog(reply));
|
log.info("Searching for mapping created since invite {} was created", getIdForLog(reply));
|
||||||
Optional<SingleLookupReply> result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true);
|
Optional<SingleLookupReply> result = lookup3pid(reply.getInvite().getMedium(), reply.getInvite().getAddress());
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
SingleLookupReply lookup = result.get();
|
SingleLookupReply lookup = result.get();
|
||||||
log.info("Found mapping for pending invite {}", getIdForLog(reply));
|
log.info("Found mapping for pending invite {}", getIdForLog(reply));
|
||||||
|
@@ -1,115 +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.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import java.io.IOException;
|
|
||||||
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.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class KeyManager {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private KeyConfig keyCfg;
|
|
||||||
|
|
||||||
private EdDSAParameterSpec keySpecs;
|
|
||||||
private EdDSAEngine signEngine;
|
|
||||||
private List<KeyPair> keys;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void build() {
|
|
||||||
try {
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException | IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCurrentIndex() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyPair getKeys(int index) {
|
|
||||||
return keys.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey(int index) {
|
|
||||||
return getKeys(index).getPrivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EdDSAPublicKey getPublicKey(int index) {
|
|
||||||
return (EdDSAPublicKey) getKeys(index).getPublic();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EdDSAParameterSpec getSpecs() {
|
|
||||||
return keySpecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPublicKeyBase64(int index) {
|
|
||||||
return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -33,7 +33,6 @@ public class SingleLookupReply {
|
|||||||
private static Gson gson = new Gson();
|
private static Gson gson = new Gson();
|
||||||
|
|
||||||
private boolean isRecursive;
|
private boolean isRecursive;
|
||||||
private boolean isSigned;
|
|
||||||
private String body;
|
private String body;
|
||||||
private SingleLookupRequest request;
|
private SingleLookupRequest request;
|
||||||
private _MatrixID mxid;
|
private _MatrixID mxid;
|
||||||
@@ -53,7 +52,6 @@ public class SingleLookupReply {
|
|||||||
reply.notAfter = Instant.ofEpochMilli(json.getNot_after());
|
reply.notAfter = Instant.ofEpochMilli(json.getNot_after());
|
||||||
reply.notBefore = Instant.ofEpochMilli(json.getNot_before());
|
reply.notBefore = Instant.ofEpochMilli(json.getNot_before());
|
||||||
reply.timestamp = Instant.ofEpochMilli(json.getTs());
|
reply.timestamp = Instant.ofEpochMilli(json.getTs());
|
||||||
reply.isSigned = json.isSigned();
|
|
||||||
} catch (JsonSyntaxException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
// stub - we only want to try, nothing more
|
// stub - we only want to try, nothing more
|
||||||
}
|
}
|
||||||
@@ -85,10 +83,6 @@ public class SingleLookupReply {
|
|||||||
return isRecursive;
|
return isRecursive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return isSigned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBody() {
|
public String getBody() {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
package io.kamax.mxisd.lookup;
|
package io.kamax.mxisd.lookup;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
|
|
||||||
public class ThreePidMapping {
|
public class ThreePidMapping {
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.lookup;
|
package io.kamax.mxisd.lookup;
|
||||||
|
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public class ThreePidValidation extends ThreePid {
|
|||||||
private Instant validation;
|
private Instant validation;
|
||||||
|
|
||||||
public ThreePidValidation(ThreePid tpid, Instant validation) {
|
public ThreePidValidation(ThreePid tpid, Instant validation) {
|
||||||
super(tpid);
|
super(tpid.getMedium(), tpid.getAddress());
|
||||||
this.validation = validation;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -53,7 +53,10 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) {
|
public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
this.providers = providers.stream().filter(IThreePidProvider::isEnabled).collect(Collectors.toList());
|
this.providers = providers.stream().filter(p -> {
|
||||||
|
log.info("3PID Provider {} is enabled: {}", p.getClass().getSimpleName(), p.isEnabled());
|
||||||
|
return p.isEnabled();
|
||||||
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@@ -156,6 +159,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
for (IThreePidProvider provider : providers) {
|
for (IThreePidProvider provider : providers) {
|
||||||
Optional<SingleLookupReply> lookupDataOpt = provider.find(request);
|
Optional<SingleLookupReply> lookupDataOpt = provider.find(request);
|
||||||
if (lookupDataOpt.isPresent()) {
|
if (lookupDataOpt.isPresent()) {
|
||||||
|
log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}",
|
||||||
|
request.getType(), request.getThreePid(), lookupDataOpt.get().getMxid().getId());
|
||||||
return lookupDataOpt;
|
return lookupDataOpt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,9 +171,13 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
(!cfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
|
(!cfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
|
||||||
) {
|
) {
|
||||||
log.info("Using bridge failover for lookup");
|
log.info("Using bridge failover for lookup");
|
||||||
return bridge.find(request);
|
Optional<SingleLookupReply> lookupDataOpt = bridge.find(request);
|
||||||
|
log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}",
|
||||||
|
request.getThreePid(), request.getId(), lookupDataOpt.get().getMxid().getId());
|
||||||
|
return lookupDataOpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("No 3PID mapping found");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import com.google.gson.JsonElement;
|
|||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.xbill.DNS.*;
|
import org.xbill.DNS.*;
|
||||||
@@ -21,18 +22,17 @@ import java.util.Optional;
|
|||||||
// FIXME placeholder, this must go in matrix-java-sdk for 1.0
|
// FIXME placeholder, this must go in matrix-java-sdk for 1.0
|
||||||
public class IdentityServerUtils {
|
public class IdentityServerUtils {
|
||||||
|
|
||||||
public static final String THREEPID_TEST_MEDIUM = "email";
|
|
||||||
public static final String THREEPID_TEST_ADDRESS = "mxisd-email-forever-unknown@forever-invalid.kamax.io";
|
|
||||||
|
|
||||||
private static Logger log = LoggerFactory.getLogger(IdentityServerUtils.class);
|
private static Logger log = LoggerFactory.getLogger(IdentityServerUtils.class);
|
||||||
private static JsonParser parser = new JsonParser();
|
private static JsonParser parser = new JsonParser();
|
||||||
|
|
||||||
public static boolean isUsable(String remote) {
|
public static boolean isUsable(String remote) {
|
||||||
|
if (StringUtils.isBlank(remote)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// FIXME use Apache HTTP client
|
// FIXME use Apache HTTP client
|
||||||
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
|
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(remote + "/_matrix/identity/api/v1/").openConnection();
|
||||||
remote + "/_matrix/identity/api/v1/lookup?medium=" + THREEPID_TEST_MEDIUM + "&address=" + THREEPID_TEST_ADDRESS
|
|
||||||
).openConnection();
|
|
||||||
// TODO turn this into a configuration property
|
// TODO turn this into a configuration property
|
||||||
rootSrvConn.setConnectTimeout(2000);
|
rootSrvConn.setConnectTimeout(2000);
|
||||||
|
|
||||||
@@ -48,13 +48,8 @@ public class IdentityServerUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.getAsJsonObject().has("address")) {
|
|
||||||
log.debug("IS {} did not send back a JSON object for single 3PID lookup");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException | JsonParseException e) {
|
} catch (IllegalArgumentException | IOException | JsonParseException e) {
|
||||||
log.info("{} is not a usable Identity Server: {}", remote, e.getMessage());
|
log.info("{} is not a usable Identity Server: {}", remote, e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -79,39 +74,35 @@ public class IdentityServerUtils {
|
|||||||
|
|
||||||
List<SRVRecord> srvRecords = new ArrayList<>();
|
List<SRVRecord> srvRecords = new ArrayList<>();
|
||||||
Record[] records = new Lookup(lookupDns, Type.SRV).run();
|
Record[] records = new Lookup(lookupDns, Type.SRV).run();
|
||||||
if (records != null) {
|
if (records == null || records.length == 0) {
|
||||||
for (Record record : records) {
|
log.info("No SRV record for {}", lookupDns);
|
||||||
log.info("Record: {}", record.toString());
|
return Optional.empty();
|
||||||
if (record.getType() == Type.SRV) {
|
}
|
||||||
if (record instanceof SRVRecord) {
|
|
||||||
srvRecords.add((SRVRecord) record);
|
|
||||||
} else {
|
|
||||||
log.warn("We requested SRV records but we got {} instead!", record.getClass().getName());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn("We request SRV type records but we got type #{} instead!", record.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority));
|
|
||||||
|
|
||||||
for (SRVRecord srvRecord : srvRecords) {
|
for (Record record : records) {
|
||||||
String baseUrl = "https://" + srvRecord.getTarget().toString(true) + ":" + srvRecord.getPort();
|
log.info("Record: {}", record.toString());
|
||||||
|
if (record.getType() == Type.SRV) {
|
||||||
|
if (record instanceof SRVRecord) {
|
||||||
|
srvRecords.add((SRVRecord) record);
|
||||||
|
} else {
|
||||||
|
log.warn("We requested SRV records but we got {} instead!", record.getClass().getName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("We request SRV type records but we got type #{} instead!", record.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority));
|
||||||
|
|
||||||
|
for (SRVRecord srvRecord : srvRecords) {
|
||||||
|
String baseUrl = "https://" + srvRecord.getTarget().toString(true) + ":" + srvRecord.getPort();
|
||||||
|
if (isUsable(baseUrl)) {
|
||||||
log.info("Found Identity Server for domain {} at {}", domainOrUrl, baseUrl);
|
log.info("Found Identity Server for domain {} at {}", domainOrUrl, baseUrl);
|
||||||
return Optional.of(baseUrl);
|
return Optional.of(baseUrl);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.info("No SRV record for {}", lookupDns);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Performing basic lookup using domain name {}", domainOrUrl);
|
log.info("Found no Identity server for domain {} at {}");
|
||||||
String baseUrl = "https://" + domainOrUrl;
|
return Optional.empty();
|
||||||
if (isUsable(baseUrl)) {
|
|
||||||
log.info("Found Identity Server for domain {} at {}", domainOrUrl, baseUrl);
|
|
||||||
return Optional.of(baseUrl);
|
|
||||||
} else {
|
|
||||||
log.info("{} is not a usable Identity Server", baseUrl);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
} catch (TextParseException e) {
|
} catch (TextParseException e) {
|
||||||
log.warn(domainOrUrl + " is not a valid domain name");
|
log.warn(domainOrUrl + " is not a valid domain name");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
@@ -46,16 +46,14 @@ public class NotificationManager {
|
|||||||
this.handlers = new HashMap<>();
|
this.handlers = new HashMap<>();
|
||||||
handlers.forEach(h -> {
|
handlers.forEach(h -> {
|
||||||
log.info("Found handler {} for medium {}", h.getId(), h.getMedium());
|
log.info("Found handler {} for medium {}", h.getId(), h.getMedium());
|
||||||
String handlerId = cfg.getHandler().get(h.getMedium());
|
String handlerId = cfg.getHandler().getOrDefault(h.getMedium(), "raw");
|
||||||
if (StringUtils.isBlank(handlerId) || StringUtils.equals(handlerId, h.getId())) {
|
if (StringUtils.equals(handlerId, h.getId())) {
|
||||||
this.handlers.put(h.getMedium(), h);
|
this.handlers.put(h.getMedium(), h);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("--- Notification handler ---");
|
log.info("--- Notification handler ---");
|
||||||
this.handlers.forEach((k, v) -> {
|
this.handlers.forEach((k, v) -> log.info("\tHandler for {}: {}", k, v.getId()));
|
||||||
log.info("\tHandler for {}: {}", k, v.getId());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private INotificationHandler ensureMedium(String medium) {
|
private INotificationHandler ensureMedium(String medium) {
|
||||||
|
58
src/main/java/io/kamax/mxisd/profile/ProfileManager.java
Normal file
58
src/main/java/io/kamax/mxisd/profile/ProfileManager.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.profile;
|
||||||
|
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ProfileManager {
|
||||||
|
|
||||||
|
private List<ProfileProvider> providers;
|
||||||
|
|
||||||
|
public ProfileManager(List<ProfileProvider> providers) {
|
||||||
|
this.providers = providers.stream()
|
||||||
|
.filter(ProfileProvider::isEnabled)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> List<T> get(Function<ProfileProvider, List<T>> function) {
|
||||||
|
return providers.stream()
|
||||||
|
.map(function)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<_ThreePid> getThreepids(_MatrixID mxid) {
|
||||||
|
return get(p -> p.getThreepids(mxid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRoles(_MatrixID mxid) {
|
||||||
|
return get(p -> p.getRoles(mxid));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
src/main/java/io/kamax/mxisd/profile/ProfileProvider.java
Normal file
36
src/main/java/io/kamax/mxisd/profile/ProfileProvider.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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.profile;
|
||||||
|
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ProfileProvider {
|
||||||
|
|
||||||
|
boolean isEnabled();
|
||||||
|
|
||||||
|
List<_ThreePid> getThreepids(_MatrixID mxid);
|
||||||
|
|
||||||
|
List<String> getRoles(_MatrixID mxid);
|
||||||
|
|
||||||
|
}
|
@@ -25,9 +25,9 @@ import com.google.i18n.phonenumbers.NumberParseException;
|
|||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
import com.google.i18n.phonenumbers.Phonenumber;
|
import com.google.i18n.phonenumbers.Phonenumber;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.ThreePid;
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.SessionConfig;
|
import io.kamax.mxisd.config.SessionConfig;
|
||||||
import io.kamax.mxisd.controller.identity.v1.io.RequestTokenResponse;
|
import io.kamax.mxisd.controller.identity.v1.io.RequestTokenResponse;
|
||||||
@@ -196,7 +196,7 @@ public class SessionMananger {
|
|||||||
storage.updateThreePidSession(session.getDao());
|
storage.updateThreePidSession(session.getDao());
|
||||||
log.info("Session {} has been validated locally", session.getId());
|
log.info("Session {} has been validated locally", session.getId());
|
||||||
|
|
||||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated()) {
|
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated() && policy.toRemote()) {
|
||||||
createRemote(sid, secret);
|
createRemote(sid, secret);
|
||||||
// FIXME make the message configurable/customizable (templates?)
|
// FIXME make the message configurable/customizable (templates?)
|
||||||
throw new MessageForClientException("You will receive a NEW code from another number. Enter it below");
|
throw new MessageForClientException("You will receive a NEW code from another number. Enter it below");
|
||||||
@@ -272,9 +272,13 @@ public class SessionMananger {
|
|||||||
|
|
||||||
List<String> servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer());
|
List<String> servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer());
|
||||||
if (servers.isEmpty()) {
|
if (servers.isEmpty()) {
|
||||||
throw new InternalServerError();
|
throw new FeatureNotAvailable("Remote 3PID sessions are enabled but server list is " +
|
||||||
|
"misconstrued (invalid ID or empty list");
|
||||||
}
|
}
|
||||||
String url = IdentityServerUtils.findIsUrlForDomain(servers.get(0)).orElseThrow(InternalServerError::new);
|
|
||||||
|
String is = servers.get(0);
|
||||||
|
String url = IdentityServerUtils.findIsUrlForDomain(is)
|
||||||
|
.orElseThrow(() -> new InternalServerError(is + " could not be resolved to an Identity server"));
|
||||||
log.info("Will use IS endpoint {}", url);
|
log.info("Will use IS endpoint {}", url);
|
||||||
|
|
||||||
String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16);
|
String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16);
|
||||||
@@ -283,13 +287,17 @@ public class SessionMananger {
|
|||||||
body.addProperty("client_secret", remoteSecret);
|
body.addProperty("client_secret", remoteSecret);
|
||||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||||
body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt());
|
body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt());
|
||||||
try {
|
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium())) {
|
||||||
Phonenumber.PhoneNumber msisdn = phoneUtil.parse("+" + session.getThreePid().getAddress(), null);
|
try {
|
||||||
String country = phoneUtil.getRegionCodeForNumber(msisdn).toUpperCase();
|
Phonenumber.PhoneNumber msisdn = phoneUtil.parse("+" + session.getThreePid().getAddress(), null);
|
||||||
body.addProperty("phone_number", phoneUtil.format(msisdn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL));
|
String country = phoneUtil.getRegionCodeForNumber(msisdn).toUpperCase();
|
||||||
body.addProperty("country", country);
|
body.addProperty("phone_number", phoneUtil.format(msisdn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL));
|
||||||
} catch (NumberParseException e) {
|
body.addProperty("country", country);
|
||||||
throw new InternalServerError(e);
|
} catch (NumberParseException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Requesting remote session with attempt {}", session.getRemoteAttempt());
|
log.info("Requesting remote session with attempt {}", session.getRemoteAttempt());
|
||||||
@@ -371,7 +379,7 @@ public class SessionMananger {
|
|||||||
|
|
||||||
if (o.has("validated_at")) {
|
if (o.has("validated_at")) {
|
||||||
ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString());
|
ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString());
|
||||||
if (session.getThreePid().equals(remoteThreePid)) { // sanity check
|
if (!session.getThreePid().equals(remoteThreePid)) { // sanity check
|
||||||
throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId());
|
throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,79 +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 com.google.gson.JsonObject;
|
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
|
||||||
import io.kamax.mxisd.key.KeyManager;
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAEngine;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class SignatureManager {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private KeyManager keyMgr;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ServerConfig srvCfg;
|
|
||||||
|
|
||||||
private EdDSAEngine signEngine;
|
|
||||||
|
|
||||||
private String sign(String message) {
|
|
||||||
try {
|
|
||||||
byte[] signRaw = signEngine.signOneShot(message.getBytes());
|
|
||||||
return Base64.getEncoder().encodeToString(signRaw);
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
throw new InternalServerError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonObject signMessageGson(String message) {
|
|
||||||
String sign = sign(message);
|
|
||||||
|
|
||||||
JsonObject keySignature = new JsonObject();
|
|
||||||
keySignature.addProperty("ed25519:" + keyMgr.getCurrentIndex(), sign);
|
|
||||||
JsonObject signature = new JsonObject();
|
|
||||||
signature.add(srvCfg.getName(), keySignature);
|
|
||||||
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void build() {
|
|
||||||
try {
|
|
||||||
signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm()));
|
|
||||||
signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex()));
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException 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