Compare commits
91 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9fba20475b | ||
|
9af5fce014 | ||
|
9843e14c1a | ||
|
60e6f1e23c | ||
|
6cdbcc69c7 | ||
|
ed7c714738 | ||
|
a9d783192b | ||
|
2bb5a734d1 | ||
|
9aa5c4cca9 | ||
|
9c4faab5d8 | ||
|
53c4ffdc4e | ||
|
e4144e923a | ||
|
791361c10d | ||
|
7c94bd4744 | ||
|
4b5eecd7e7 | ||
|
a6968fb7e9 | ||
|
d4853b1154 | ||
|
89df4b2425 | ||
|
0f89121b98 | ||
|
8a40ca185b | ||
|
5baeb42623 | ||
|
072e5f66cb | ||
|
b2f41d689b | ||
|
9b4aff58c7 | ||
|
a20e41574d | ||
|
72977d65ae | ||
|
7555fff1a5 | ||
|
aed12e5536 | ||
|
75efd9921d | ||
|
9219bd4723 | ||
|
73526be2ac | ||
|
b827efca2c | ||
|
6b7a4c8a23 | ||
|
47f6239268 | ||
|
0d6f65b469 | ||
|
be915aed94 | ||
|
ce938bb4a5 | ||
|
15db563e8d | ||
|
82a538c750 | ||
|
84ca8ebbd9 | ||
|
774ebf4fa8 | ||
|
eb1326c56a | ||
|
10cdb4360e | ||
|
17ebc2a421 | ||
|
cbb9fced8d | ||
|
7509174611 | ||
|
51d9225dda | ||
|
6216113400 | ||
|
cb32441959 | ||
|
0ec4df2c06 | ||
|
86b880069b | ||
|
a97273fe77 | ||
|
f9daf4d58a | ||
|
9e4cabb69b | ||
|
0b81de3cd0 | ||
|
698a16ec17 | ||
|
619b70d860 | ||
|
494c9e3941 | ||
|
0786a6520f | ||
|
430136c391 | ||
|
eda4404335 | ||
|
c52034b18a | ||
|
8d346037b7 | ||
|
14ad4435bc | ||
|
94441d0446 | ||
|
b4776b50e2 | ||
|
2458b38b75 | ||
|
249e28a8b5 | ||
|
8ba8756871 | ||
|
ba9e2d6121 | ||
|
f042b82a50 | ||
|
59071177ad | ||
|
6450cd1f20 | ||
|
90bc244f3e | ||
|
6e52a509db | ||
|
5ca666981a | ||
|
43fe8b1aec | ||
|
a0270c7d01 | ||
|
703044d06a | ||
|
add6ed8fd9 | ||
|
baed894ff8 | ||
|
14e095a147 | ||
|
bc8795e940 | ||
|
5521c0c338 | ||
|
614b3440e2 | ||
|
d0fd9fb9b0 | ||
|
1232e9ce79 | ||
|
a47a983c10 | ||
|
fbb0d7c7ba | ||
|
f1dd309551 | ||
|
36f22e5ca6 |
16
DockerfileX
Normal file
16
DockerfileX
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM --platform=$BUILDPLATFORM openjdk:11.0.7-jre-slim
|
||||
|
||||
VOLUME /etc/ma1sd
|
||||
VOLUME /var/ma1sd
|
||||
EXPOSE 8090
|
||||
|
||||
ENV JAVA_OPTS=""
|
||||
ENV CONF_FILE_PATH="/etc/ma1sd/ma1sd.yaml"
|
||||
ENV SIGN_KEY_PATH="/var/ma1sd/sign.key"
|
||||
ENV SQLITE_DATABASE_PATH="/var/ma1sd/ma1sd.db"
|
||||
|
||||
CMD [ "/start.sh" ]
|
||||
|
||||
ADD src/docker/start.sh /start.sh
|
||||
ADD src/script/ma1sd /app/ma1sd
|
||||
ADD build/libs/ma1sd.jar /app/ma1sd.jar
|
@@ -15,7 +15,8 @@ ma1sd - Federated Matrix Identity Server
|
||||
|
||||
---
|
||||
|
||||
* This project is a fork of the https://github.com/kamax-matrix/mxisd which has been archived and no longer supported. *
|
||||
* This project is a fork (not successor) of the https://github.com/kamax-matrix/mxisd, which has been archived and no longer maintained as a standalone product.
|
||||
Also, ma1sd is supported by the volunteer not developers of the original project.
|
||||
|
||||
---
|
||||
|
||||
|
63
build.gradle
63
build.gradle
@@ -78,7 +78,7 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +94,12 @@ dependencies {
|
||||
compile 'commons-io:commons-io:2.6'
|
||||
|
||||
// Config management
|
||||
compile 'org.yaml:snakeyaml:1.24'
|
||||
compile 'org.yaml:snakeyaml:1.25'
|
||||
|
||||
// Dependencies from old Matrix-java-sdk
|
||||
compile 'org.apache.commons:commons-lang3:3.9'
|
||||
compile 'com.squareup.okhttp3:okhttp:4.0.1'
|
||||
compile 'commons-codec:commons-codec:1.12'
|
||||
compile 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
compile 'commons-codec:commons-codec:1.13'
|
||||
|
||||
// ORMLite
|
||||
compile 'com.j256.ormlite:ormlite-jdbc:5.1'
|
||||
@@ -108,16 +108,16 @@ dependencies {
|
||||
compile 'net.i2p.crypto:eddsa:0.3.0'
|
||||
|
||||
// LDAP connector
|
||||
compile 'org.apache.directory.api:api-all:1.0.0'
|
||||
compile 'org.apache.directory.api:api-all:1.0.3'
|
||||
|
||||
// DNS lookups
|
||||
compile 'dnsjava:dnsjava:2.1.9'
|
||||
|
||||
// HTTP connections
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.9'
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.10'
|
||||
|
||||
// Phone numbers validation
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.15'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.22'
|
||||
|
||||
// E-mail sending
|
||||
compile 'javax.mail:javax.mail-api:1.6.2'
|
||||
@@ -133,13 +133,13 @@ dependencies {
|
||||
compile 'org.xerial:sqlite-jdbc:3.28.0'
|
||||
|
||||
// PostgreSQL
|
||||
compile 'org.postgresql:postgresql:42.2.6'
|
||||
compile 'org.postgresql:postgresql:42.2.8'
|
||||
|
||||
// MariaDB/MySQL
|
||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.4.2'
|
||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.5.1'
|
||||
|
||||
// Twilio SDK for SMS
|
||||
compile 'com.twilio.sdk:twilio:7.40.1'
|
||||
compile 'com.twilio.sdk:twilio:7.45.0'
|
||||
|
||||
// SendGrid SDK to send emails from GCE
|
||||
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
||||
@@ -148,15 +148,15 @@ dependencies {
|
||||
compile 'org.zeroturnaround:zt-exec:1.11'
|
||||
|
||||
// HTTP server
|
||||
compile 'io.undertow:undertow-core:2.0.22.Final'
|
||||
compile 'io.undertow:undertow-core:2.0.27.Final'
|
||||
|
||||
// Command parser for AS interface
|
||||
implementation 'commons-cli:commons-cli:1.4'
|
||||
|
||||
testCompile 'junit:junit:4.13-beta-3'
|
||||
testCompile 'com.github.tomakehurst:wiremock:2.24.0'
|
||||
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.11'
|
||||
testCompile 'com.icegreen:greenmail:1.5.10'
|
||||
testCompile 'junit:junit:4.13-rc-1'
|
||||
testCompile 'com.github.tomakehurst:wiremock:2.25.1'
|
||||
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.12'
|
||||
testCompile 'com.icegreen:greenmail:1.5.11'
|
||||
}
|
||||
|
||||
jar {
|
||||
@@ -274,6 +274,27 @@ task dockerBuild(type: Exec, dependsOn: shadowJar) {
|
||||
}
|
||||
}
|
||||
|
||||
task dockerBuildX(type: Exec, dependsOn: shadowJar) {
|
||||
commandLine 'docker', 'buildx', 'build', '--load', '--platform', 'linux/arm64', '-t', dockerImageTag + '-arm64', project.rootDir
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'buildx', 'build', '--load', '--platform', 'linux/amd64', '-t', dockerImageTag + '-amd64', project.rootDir
|
||||
}
|
||||
|
||||
exec {
|
||||
commandLine 'docker', 'tag', dockerImageTag + '-arm64', "${dockerImageName}:latest-arm64-dev"
|
||||
}
|
||||
|
||||
exec {
|
||||
commandLine 'docker', 'tag', dockerImageTag + '-amd64', "${dockerImageName}:latest-amd64-dev"
|
||||
}
|
||||
|
||||
exec {
|
||||
commandLine 'docker', 'tag', dockerImageTag + '-amd64', "${dockerImageName}:latest-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerPush(type: Exec) {
|
||||
commandLine 'docker', 'push', dockerImageTag
|
||||
|
||||
@@ -283,3 +304,15 @@ task dockerPush(type: Exec) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerPushX(type: Exec) {
|
||||
commandLine 'docker', 'push', dockerImageTag
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-dev"
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-amd64-dev"
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-arm64-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
146
docs/MSC2140_MSC2134.md
Normal file
146
docs/MSC2140_MSC2134.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# MSC2140
|
||||
|
||||
## V1 vs V2
|
||||
In the [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) the v2 prefix was introduced.
|
||||
|
||||
Default values:
|
||||
```.yaml
|
||||
matrix:
|
||||
v1: true # deprecated
|
||||
v2: false
|
||||
```
|
||||
|
||||
To disable change value to `false`.
|
||||
|
||||
NOTE: the v1 is deprecated, therefore recommend to use only v2 and disable v1 (default value can be ommited):
|
||||
```.yaml
|
||||
matrix:
|
||||
v1: false
|
||||
```
|
||||
NOTE: Riot Web version 1.5.5 and below checks the v1 for backward compatibility.
|
||||
|
||||
NOTE: v2 disabled by default in order to preserve backward compatibility.
|
||||
|
||||
## Terms
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
Administrator can omit terms configuration. In this case the terms checking will be disabled.
|
||||
|
||||
Example:
|
||||
```.yaml
|
||||
policy:
|
||||
policies:
|
||||
term_name: # term name
|
||||
version: 1.0 # version
|
||||
terms:
|
||||
en: # lang
|
||||
name: term name en # localized name
|
||||
url: https://ma1sd.host.tld/term_en.html # localized url
|
||||
fe: # lang
|
||||
name: term name fr # localized name
|
||||
url: https://ma1sd.host.tld/term_fr.html # localized url
|
||||
regexp:
|
||||
- '/_matrix/identity/v2/account.*'
|
||||
- '/_matrix/identity/v2/hash_details'
|
||||
- '/_matrix/identity/v2/lookup'
|
||||
```
|
||||
Where:
|
||||
|
||||
- `term_name` -- name of the terms.
|
||||
- `version` -- the terms version.
|
||||
- `lang` -- the term language.
|
||||
- `name` -- the name of the term.
|
||||
- `url` -- the url of the term. Might be any url (i.e. from another host) for a html page.
|
||||
- `regexp` -- regexp patterns for API which should be available only after accepting the terms.
|
||||
|
||||
API will be checks for accepted terms only with authorization.
|
||||
There are the next API:
|
||||
- [`GET /_matrix/identity/v2/account`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-account) - Gets information about what user owns the access token used in the request.
|
||||
- [`POST /_matrix/identity/v2/account/logout`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-account-logout) - Logs out the access token, preventing it from being used to authenticate future requests to the server.
|
||||
- [`GET /_matrix/identity/v2/hash_details`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-hash-details) - Gets parameters for hashing identifiers from the server. This can include any of the algorithms defined in this specification.
|
||||
- [`POST /_matrix/identity/v2/lookup`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-lookup) - Looks up the set of Matrix User IDs which have bound the 3PIDs given, if bindings are available. Note that the format of the addresses is defined later in this specification.
|
||||
- [`POST /_matrix/identity/v2/validate/email/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-requesttoken) - Create a session for validating an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`GET /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-requesttoken) - Create a session for validating a phone number.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/3pid/getValidated3pid`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-3pid-getvalidated3pid) - Determines if a given 3pid has been validated by a user.
|
||||
- [`POST /_matrix/identity/v2/3pid/bind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-bind) - Publish an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/3pid/unbind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-unbind) - Remove an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/store-invite`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-store-invite) - Store pending invitations to a user's 3pid.
|
||||
- [`POST /_matrix/identity/v2/sign-ed25519`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519) - Sign invitation details.
|
||||
|
||||
There is only one exception: [`POST /_matrix/identity/v2/terms`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-terms) which uses for accepting the terms and requires the authorization.
|
||||
|
||||
## [Hash lookup](https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md)
|
||||
|
||||
Hashes and the pepper updates together according to the `rotationPolicy`.
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
In case the `none` algorithms ma1sd will be lookup using the v1 bulk API.
|
||||
|
||||
```.yaml
|
||||
hashing:
|
||||
enabled: true # enable or disable the hash lookup MSC2140 (default is false)
|
||||
pepperLength: 20 # length of the pepper value (default is 20)
|
||||
rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
algorithms:
|
||||
- none # the same as v1 bulk lookup
|
||||
- sha256 # hash the 3PID and pepper.
|
||||
delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
```
|
||||
|
||||
When enabled and client requests the `none` algorithms then hash lookups works as v1 bulk lookup.
|
||||
|
||||
Delay specified in the format: `2d 4h 12m 34s` - this means 2 days 4 hours 12 minutes and 34 seconds. Zero units may be omitted. For example:
|
||||
|
||||
- 12s - 12 seconds
|
||||
- 3m - 3 minutes
|
||||
- 5m 6s - 5 minutes and 6 seconds
|
||||
- 6h 3s - 6 hours and 3 seconds
|
||||
|
||||
|
||||
Sha256 algorithm supports only sql, memory and exec 3PID providers.
|
||||
For sql provider (i.e. for the `synapseSql`):
|
||||
```.yaml
|
||||
synapseSql:
|
||||
lookup:
|
||||
query: 'select user_id as mxid, medium, address from user_threepid_id_server' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
For general sql provider:
|
||||
```.yaml
|
||||
sql:
|
||||
lookup:
|
||||
query: 'select user as mxid, field1 as medium, field2 as address from some_table' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
Each query should return the `mxid`, `medium` and `address` fields.
|
||||
|
||||
|
||||
For memory providers:
|
||||
```.yaml
|
||||
memory:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
For exec providers:
|
||||
```.yaml
|
||||
exec:
|
||||
identity:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
For ldap providers:
|
||||
```.yaml
|
||||
ldap:
|
||||
lookup: true
|
||||
```
|
||||
|
||||
NOTE: Federation requests work only with `none` algorithms.
|
||||
|
@@ -9,6 +9,8 @@
|
||||
## Binaries
|
||||
### Requirements
|
||||
- JDK 1.8
|
||||
- OpenJDK 11
|
||||
- OpenJDK 14
|
||||
|
||||
### Build
|
||||
```bash
|
||||
@@ -70,5 +72,13 @@ Then follow the instruction in the [Debian package](install/debian.md) document.
|
||||
```
|
||||
Then follow the instructions in the [Docker install](install/docker.md#configure) document.
|
||||
|
||||
### Multi-platform builds
|
||||
|
||||
Provided with experimental docker feature [buildx](https://docs.docker.com/buildx/working-with-buildx/)
|
||||
To build the arm64 and amd64 images run:
|
||||
```bash
|
||||
./gradlew dockerBuildX
|
||||
```
|
||||
|
||||
## Next steps
|
||||
- [Integrate with your infrastructure](getting-started.md#integrate)
|
||||
|
@@ -48,6 +48,9 @@ Create a list under the label `myOtherServers` containing two Identity servers:
|
||||
## Unbind (MSC1915)
|
||||
- `session.policy.unbind.enabled`: Enable or disable unbind functionality (MSC1915). (Defaults to true).
|
||||
|
||||
## Hash lookups, Term and others (MSC2140, MSC2134)
|
||||
See the [dedicated document](MSC2140_MSC2134.md) for configuration.
|
||||
|
||||
*Warning*: Unbind check incoming request by two ways:
|
||||
- session validation.
|
||||
- request signature via `X-Matrix` header and uses `server.publicUrl` property to construct the signing json;
|
||||
@@ -55,7 +58,53 @@ Commonly the `server.publicUrl` should be the same value as the `trusted_third_p
|
||||
|
||||
## Storage
|
||||
### SQLite
|
||||
`storage.provider.sqlite.database`: Absolute location of the SQLite database
|
||||
```yaml
|
||||
storage:
|
||||
backend: sqlite # default
|
||||
provider:
|
||||
sqlite:
|
||||
database: /var/lib/ma1sd/store.db # Absolute location of the SQLite database
|
||||
```
|
||||
|
||||
### Postgresql
|
||||
```yaml
|
||||
storage:
|
||||
backend: postgresql
|
||||
provider:
|
||||
postgresql:
|
||||
database: //localhost:5432/ma1sd
|
||||
username: ma1sd
|
||||
password: secret_password
|
||||
```
|
||||
See [the migration instruction](migration-to-postgresql.md) from sqlite to postgresql
|
||||
|
||||
|
||||
## Logging
|
||||
```yaml
|
||||
logging:
|
||||
root: error # default level for all loggers (apps and thirdparty libraries)
|
||||
app: info # log level only for the ma1sd
|
||||
requests: false # log request and response
|
||||
```
|
||||
|
||||
Possible value: `trace`, `debug`, `info`, `warn`, `error`, `off`.
|
||||
|
||||
Default value for root level: `info`.
|
||||
|
||||
Value for app level can be specified via `MA1SD_LOG_LEVEL` environment variable, configuration or start options.
|
||||
|
||||
Default value for app level: `info`.
|
||||
|
||||
| start option | equivalent configuration |
|
||||
| --- | --- |
|
||||
| | app: info |
|
||||
| -v | app: debug |
|
||||
| -vv | app: trace |
|
||||
|
||||
#### WARNING
|
||||
|
||||
The setting `logging.requests` *MUST NOT* be used in production due it prints full unmasked request and response into the log and can be cause of the data leak.
|
||||
This setting can be used only to testing and debugging errors.
|
||||
|
||||
## Identity stores
|
||||
See the [Identity stores](stores/README.md) for specific configuration
|
||||
|
@@ -56,8 +56,7 @@ Accounts cannot currently migrate/move from one server to another.
|
||||
See a [brief explanation document](concepts.md) about Matrix and ma1sd concepts and vocabulary.
|
||||
|
||||
### I already use the synapse LDAP3 auth provider. Why should I care about ma1sd?
|
||||
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained despite
|
||||
saying so and only handles on specific flow: validate credentials at login.
|
||||
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) only handles one specific flow: validate credentials at login.
|
||||
|
||||
It does not:
|
||||
- Auto-provision user profiles
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Identity
|
||||
Implementation of the [Identity Service API r0.2.0](https://matrix.org/docs/spec/identity_service/r0.2.0.html).
|
||||
Implementation of the [Identity Service API r0.3.0](https://matrix.org/docs/spec/identity_service/r0.3.0.html).
|
||||
|
||||
- [Lookups](#lookups)
|
||||
- [Invitations](#invitations)
|
||||
|
41
docs/migration-to-postgresql.md
Normal file
41
docs/migration-to-postgresql.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Migration from sqlite to postgresql
|
||||
|
||||
Starting from the version 2.3.0 ma1sd support postgresql for internal storage in addition to sqlite (parameters `storage.backend`).
|
||||
|
||||
#### Migration steps
|
||||
|
||||
1. create the postgresql database and user for ma1sd storage
|
||||
2. create a backup for sqlite storage (default location: /var/lib/ma1sd/store.db)
|
||||
3. migrate data from sqlite to postgresql
|
||||
4. change ma1sd configuration to use the postgresql
|
||||
|
||||
For data migration is it possible to use https://pgloader.io tool.
|
||||
|
||||
Example of the migration command:
|
||||
```shell script
|
||||
pgloader --with "quote identifiers" /path/to/store.db pgsql://ma1sd_user:ma1sd_password@host:port/database
|
||||
```
|
||||
or (short version for database on localhost)
|
||||
```shell script
|
||||
pgloader --with "quote identifiers" /path/to/store.db pgsql://ma1sd_user:ma1sd_password@localhost/ma1sd
|
||||
```
|
||||
|
||||
An option `--with "quote identifies"` used to create case sensitive tables.
|
||||
ma1sd_user - postgresql user for ma1sd.
|
||||
ma1sd_password - password of the postgresql user.
|
||||
host - postgresql host
|
||||
post - database port (default 5432)
|
||||
database - database name.
|
||||
|
||||
|
||||
Configuration example for postgresql storage:
|
||||
```yaml
|
||||
storage:
|
||||
backend: postgresql
|
||||
provider:
|
||||
postgresql:
|
||||
database: '//localhost/ma1sd' # or full variant //192.168.1.100:5432/ma1sd_database
|
||||
username: 'ma1sd_user'
|
||||
password: 'ma1sd_password'
|
||||
```
|
||||
|
@@ -89,7 +89,7 @@ ldap:
|
||||
#### 3PIDs
|
||||
You can also change the attribute lists for 3PID, like email or phone numbers.
|
||||
|
||||
The following example would overwrite the [default list of attributes](../../src/main/java/io/kamax/ma1sd/config/ldap/LdapConfig.java#L64)
|
||||
The following example would overwrite the [default list of attributes](../../src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java#L64)
|
||||
for emails and phone number:
|
||||
```yaml
|
||||
ldap:
|
||||
|
@@ -136,7 +136,7 @@ sql:
|
||||
|
||||
```
|
||||
For the `role` query, `type` can be used to tell ma1sd how to inject the User ID in the query:
|
||||
- `localpart` will extract and set only the localpart.
|
||||
- `uid` will extract and set only the localpart.
|
||||
- `mxid` will use the ID as-is.
|
||||
|
||||
On each query, the first parameter `?` is set as a string with the corresponding ID format.
|
||||
|
@@ -103,8 +103,8 @@ session:
|
||||
validation:
|
||||
enabled: true
|
||||
unbind:
|
||||
notification:
|
||||
enabled: true
|
||||
notifications: true
|
||||
enabled: true
|
||||
|
||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||
# CONFIGURATION EXAMPLE
|
||||
@@ -115,7 +115,7 @@ are allowed to do in terms of 3PID sessions. The policy has a global on/off swit
|
||||
|
||||
---
|
||||
|
||||
`unbind` controls warning notifications for 3PID removal.
|
||||
`unbind` controls warning notifications for 3PID removal. Setting `notifications` for `unbind` to false will prevent unbind emails from sending.
|
||||
|
||||
### Web views
|
||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
#Thu Jul 04 22:47:59 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||
#Thu Dec 05 22:39:36 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
|
6
gradlew
vendored
6
gradlew
vendored
@@ -7,7 +7,7 @@
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -125,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -5,7 +5,7 @@
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
@@ -21,6 +21,8 @@
|
||||
#
|
||||
matrix:
|
||||
domain: ''
|
||||
v1: true # deprecated
|
||||
v2: true # MSC2140 API v2. Riot require enabled V2 API.
|
||||
|
||||
|
||||
################
|
||||
@@ -49,10 +51,39 @@ key:
|
||||
# - /var/lib/ma1sd/store.db
|
||||
#
|
||||
storage:
|
||||
# backend: sqlite # or postgresql
|
||||
provider:
|
||||
sqlite:
|
||||
database: '/path/to/ma1sd.db'
|
||||
|
||||
# postgresql:
|
||||
# # Wrap all string values with quotes to avoid yaml parsing mistakes
|
||||
# database: '//localhost/ma1sd' # or full variant //192.168.1.100:5432/ma1sd_database
|
||||
# username: 'ma1sd_user'
|
||||
# password: 'ma1sd_password'
|
||||
#
|
||||
# # Pool configuration for postgresql backend.
|
||||
# #######
|
||||
# # Enable or disable pooling
|
||||
# pool: false
|
||||
#
|
||||
# #######
|
||||
# # Check database connection before get from pool
|
||||
# testBeforeGetFromPool: false # or true
|
||||
#
|
||||
# #######
|
||||
# # There is an internal thread which checks each of the database connections as a keep-alive mechanism. This set the
|
||||
# # number of milliseconds it sleeps between checks -- default is 30000. To disable the checking thread, set this to
|
||||
# # 0 before you start using the connection source.
|
||||
# checkConnectionsEveryMillis: 30000
|
||||
#
|
||||
# #######
|
||||
# # Set the number of connections that can be unused in the available list.
|
||||
# maxConnectionsFree: 5
|
||||
#
|
||||
# #######
|
||||
# # Set the number of milliseconds that a connection can stay open before being closed. Set to 9223372036854775807 to have
|
||||
# # the connections never expire.
|
||||
# maxConnectionAgeMillis: 3600000
|
||||
|
||||
###################
|
||||
# Identity Stores #
|
||||
@@ -109,3 +140,65 @@ threepid:
|
||||
|
||||
# Password for the account
|
||||
password: "ThePassword"
|
||||
|
||||
|
||||
#### MSC2134 (hash lookup)
|
||||
|
||||
#hashing:
|
||||
# enabled: false # enable or disable the hash lookup MSC2140 (default is false)
|
||||
# pepperLength: 20 # length of the pepper value (default is 20)
|
||||
# rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
# hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
# algorithms:
|
||||
# - none # the same as v1 bulk lookup
|
||||
# - sha256 # hash the 3PID and pepper.
|
||||
# delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
# requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
|
||||
### hash lookup for synapseSql provider.
|
||||
# synapseSql:
|
||||
# lookup:
|
||||
# query: 'select user_id as mxid, medium, address from user_threepid_id_server' # query for retrive 3PIDs for hashes.
|
||||
# legacyRoomNames: false # use the old query to get room names.
|
||||
|
||||
### hash lookup for ldap provider (with example of the ldap configuration)
|
||||
# ldap:
|
||||
# enabled: true
|
||||
# lookup: true # hash lookup
|
||||
# connection:
|
||||
# host: 'ldap.domain.tld'
|
||||
# port: 389
|
||||
# bindDn: 'cn=admin,dc=domain,dc=tld'
|
||||
# bindPassword: 'Secret'
|
||||
# baseDNs:
|
||||
# - 'dc=domain,dc=tld'
|
||||
# attribute:
|
||||
# uid:
|
||||
# type: 'uid' # or mxid
|
||||
# value: 'cn'
|
||||
# name: 'displayName'
|
||||
# identity:
|
||||
# filter: '(objectClass=inetOrgPerson)'
|
||||
|
||||
#### MSC2140 (Terms)
|
||||
#policy:
|
||||
# policies:
|
||||
# term_name: # term name
|
||||
# version: 1.0 # version
|
||||
# terms:
|
||||
# en: # lang
|
||||
# name: term name en # localized name
|
||||
# url: https://ma1sd.host.tld/term_en.html # localized url
|
||||
# fe: # lang
|
||||
# name: term name fr # localized name
|
||||
# url: https://ma1sd.host.tld/term_fr.html # localized url
|
||||
# regexp:
|
||||
# - '/_matrix/identity/v2/account.*'
|
||||
# - '/_matrix/identity/v2/hash_details'
|
||||
# - '/_matrix/identity/v2/lookup'
|
||||
#
|
||||
|
||||
# logging:
|
||||
# root: error # default level for all loggers (apps and thirdparty libraries)
|
||||
# app: info # log level only for the ma1sd
|
||||
# requests: false # or true to dump full requests and responses
|
||||
|
@@ -27,7 +27,7 @@ public class ThreePid implements _ThreePid {
|
||||
|
||||
public ThreePid(String medium, String address) {
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
this.address = address.toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix.codec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class MxSha256 {
|
||||
|
||||
private MessageDigest md;
|
||||
|
||||
public MxSha256() {
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String hash(byte[] data) {
|
||||
return MxBase64.encode(md.digest(data));
|
||||
}
|
||||
|
||||
public String hash(String data) {
|
||||
return hash(data.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
@@ -20,9 +20,16 @@
|
||||
|
||||
package io.kamax.mxisd;
|
||||
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.AuthorizationHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.CheckTermsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.InternalInfoHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.OptionsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.RequestDumpingHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.SaneHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.as.v1.AsNotFoundHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler;
|
||||
@@ -31,19 +38,47 @@ import io.kamax.mxisd.http.undertow.handler.auth.RestAuthHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginGetHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginPostHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountGetUserInfoHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountLogoutHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountRegisterHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.directory.v1.UserDirectorySearchHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.*;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.EphemeralKeyIsValidHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.HelloHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.KeyGetHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.RegularKeyIsValidHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionStartHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidBindHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidGetValidatedHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidUnbindHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationGetHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationPostHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SignEd25519Handler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.StoreInviteHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.BulkLookupHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.SingleLookupHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v2.HashDetailsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v2.HashLookupHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.invite.v1.RoomInviteHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.profile.v1.InternalProfileHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.profile.v1.ProfileHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.register.v1.Register3pidRequestTokenHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.status.StatusHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.status.VersionHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.term.v2.AcceptTermsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.term.v2.GetTermsHandler;
|
||||
import io.kamax.mxisd.matrix.IdentityServiceAPI;
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.RoutingHandler;
|
||||
import io.undertow.util.HttpString;
|
||||
import io.undertow.util.Methods;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class HttpMxisd {
|
||||
|
||||
@@ -66,81 +101,178 @@ public class HttpMxisd {
|
||||
public void start() {
|
||||
m.start();
|
||||
|
||||
HttpHandler helloHandler = SaneHandler.around(new HelloHandler());
|
||||
HttpHandler asUserHandler = sane(new AsUserHandler(m.getAs()));
|
||||
HttpHandler asTxnHandler = sane(new AsTransactionHandler(m.getAs()));
|
||||
HttpHandler asNotFoundHandler = sane(new AsNotFoundHandler(m.getAs()));
|
||||
|
||||
HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs()));
|
||||
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
|
||||
HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs()));
|
||||
final RoutingHandler handler = Handlers.routing()
|
||||
.add("OPTIONS", "/**", sane(new OptionsHandler()))
|
||||
|
||||
HttpHandler storeInvHandler = SaneHandler.around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager()));
|
||||
// Status endpoints
|
||||
.get(StatusHandler.Path, sane(new StatusHandler()))
|
||||
.get(VersionHandler.Path, sane(new VersionHandler()))
|
||||
|
||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
|
||||
// Authentication endpoints
|
||||
.get(LoginHandler.Path, sane(new LoginGetHandler(m.getAuth(), m.getHttpClient())))
|
||||
.post(LoginHandler.Path, sane(new LoginPostHandler(m.getAuth())))
|
||||
.post(RestAuthHandler.Path, sane(new RestAuthHandler(m.getAuth())))
|
||||
|
||||
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, sane(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
|
||||
// Status endpoints
|
||||
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
|
||||
.get(VersionHandler.Path, SaneHandler.around(new VersionHandler()))
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, sane(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, sane(new InternalProfileHandler(m.getProfile())))
|
||||
|
||||
// Authentication endpoints
|
||||
.get(LoginHandler.Path, SaneHandler.around(new LoginGetHandler(m.getAuth(), m.getHttpClient())))
|
||||
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
|
||||
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path,
|
||||
sane(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
// Invite endpoints
|
||||
.post(RoomInviteHandler.Path, sane(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||
|
||||
// Key endpoints
|
||||
.get(KeyGetHandler.Path, SaneHandler.around(new KeyGetHandler(m.getKeyManager())))
|
||||
.get(RegularKeyIsValidHandler.Path, SaneHandler.around(new RegularKeyIsValidHandler(m.getKeyManager())))
|
||||
.get(EphemeralKeyIsValidHandler.Path, SaneHandler.around(new EphemeralKeyIsValidHandler(m.getKeyManager())))
|
||||
// Application Service endpoints
|
||||
.get(AsUserHandler.Path, asUserHandler)
|
||||
.get("/_matrix/app/v1/rooms/**", asNotFoundHandler)
|
||||
.put(AsTransactionHandler.Path, asTxnHandler)
|
||||
|
||||
// Identity endpoints
|
||||
.get(HelloHandler.Path, helloHandler)
|
||||
.get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
|
||||
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())))
|
||||
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity())))
|
||||
.post(StoreInviteHandler.Path, storeInvHandler)
|
||||
.post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession())))
|
||||
.get(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationGetHandler(m.getSession(), m.getConfig())))
|
||||
.post(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationPostHandler(m.getSession())))
|
||||
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
||||
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvite(), m.getSign())))
|
||||
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
||||
.post(SignEd25519Handler.Path, SaneHandler.around(new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())))
|
||||
.get("/users/{" + AsUserHandler.ID + "}", asUserHandler) // Legacy endpoint
|
||||
.get("/rooms/**", asNotFoundHandler) // Legacy endpoint
|
||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path, SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
|
||||
// Invite endpoints
|
||||
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||
|
||||
// Application Service endpoints
|
||||
.get(AsUserHandler.Path, asUserHandler)
|
||||
.get("/_matrix/app/v1/rooms/**", asNotFoundHandler)
|
||||
.put(AsTransactionHandler.Path, asTxnHandler)
|
||||
|
||||
.get("/users/{" + AsUserHandler.ID + "}", asUserHandler) // Legacy endpoint
|
||||
.get("/rooms/**", asNotFoundHandler) // Legacy endpoint
|
||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||
|
||||
// Banned endpoints
|
||||
.get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler()))
|
||||
|
||||
).build();
|
||||
// Banned endpoints
|
||||
.get(InternalInfoHandler.Path, sane(new InternalInfoHandler()));
|
||||
keyEndpoints(handler);
|
||||
identityEndpoints(handler);
|
||||
termsEndpoints(handler);
|
||||
hashEndpoints(handler);
|
||||
accountEndpoints(handler);
|
||||
ServerConfig serverConfig = m.getConfig().getServer();
|
||||
httpSrv = Undertow.builder().addHttpListener(serverConfig.getPort(), serverConfig.getHostname()).setHandler(handler).build();
|
||||
|
||||
httpSrv.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Because it might have never been initialized if an exception is thrown early
|
||||
if (Objects.nonNull(httpSrv)) httpSrv.stop();
|
||||
if (Objects.nonNull(httpSrv)) {
|
||||
httpSrv.stop();
|
||||
}
|
||||
|
||||
m.stop();
|
||||
}
|
||||
|
||||
private void keyEndpoints(RoutingHandler routingHandler) {
|
||||
addEndpoints(routingHandler, Methods.GET, false,
|
||||
new KeyGetHandler(m.getKeyManager()),
|
||||
new RegularKeyIsValidHandler(m.getKeyManager()),
|
||||
new EphemeralKeyIsValidHandler(m.getKeyManager())
|
||||
);
|
||||
}
|
||||
|
||||
private void identityEndpoints(RoutingHandler routingHandler) {
|
||||
// Legacy v1
|
||||
routingHandler.get(SingleLookupHandler.Path, sane(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())));
|
||||
routingHandler.post(BulkLookupHandler.Path, sane(new BulkLookupHandler(m.getIdentity())));
|
||||
|
||||
addEndpoints(routingHandler, Methods.GET, false, new HelloHandler());
|
||||
|
||||
addEndpoints(routingHandler, Methods.GET, true,
|
||||
new SessionValidationGetHandler(m.getSession(), m.getConfig()),
|
||||
new SessionTpidGetValidatedHandler(m.getSession())
|
||||
);
|
||||
addEndpoints(routingHandler, Methods.POST, true,
|
||||
new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager()),
|
||||
new SessionStartHandler(m.getSession()),
|
||||
new SessionValidationPostHandler(m.getSession()),
|
||||
new SessionTpidBindHandler(m.getSession(), m.getInvite(), m.getSign()),
|
||||
new SessionTpidUnbindHandler(m.getSession()),
|
||||
new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())
|
||||
);
|
||||
}
|
||||
|
||||
private void accountEndpoints(RoutingHandler routingHandler) {
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
routingHandler.post(AccountRegisterHandler.Path, sane(new AccountRegisterHandler(m.getAccMgr())));
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new AccountGetUserInfoHandler(m.getAccMgr()),
|
||||
AccountGetUserInfoHandler.Path, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new AccountLogoutHandler(m.getAccMgr()),
|
||||
AccountLogoutHandler.Path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void termsEndpoints(RoutingHandler routingHandler) {
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
routingHandler.get(GetTermsHandler.PATH, sane(new GetTermsHandler(m.getConfig().getPolicy())));
|
||||
routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr())));
|
||||
}
|
||||
}
|
||||
|
||||
private void hashEndpoints(RoutingHandler routingHandler) {
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new HashDetailsHandler(m.getHashManager()),
|
||||
HashDetailsHandler.PATH, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
|
||||
new HashLookupHandler(m.getIdentity(), m.getHashManager()), HashLookupHandler.Path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
|
||||
for (ApiHandler handler : handlers) {
|
||||
attachHandler(routingHandler, method, handler, useAuthorization, sane(handler));
|
||||
}
|
||||
}
|
||||
|
||||
private void attachHandler(RoutingHandler routingHandler, HttpString method, ApiHandler apiHandler, boolean useAuthorization,
|
||||
HttpHandler httpHandler) {
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV1()) {
|
||||
routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), sane(httpHandler));
|
||||
}
|
||||
if (matrixConfig.isV2()) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, method, httpHandler, apiHandler.getPath(IdentityServiceAPI.V2),
|
||||
useAuthorization);
|
||||
}
|
||||
}
|
||||
|
||||
private void wrapWithTokenAndAuthorizationHandlers(RoutingHandler routingHandler, HttpString method, HttpHandler httpHandler,
|
||||
String url, boolean useAuthorization) {
|
||||
List<PolicyConfig.PolicyObject> policyObjects = getPolicyObjects(url);
|
||||
HttpHandler wrappedHandler;
|
||||
if (useAuthorization) {
|
||||
wrappedHandler = policyObjects.isEmpty() ? httpHandler : CheckTermsHandler.around(m.getAccMgr(), httpHandler, policyObjects);
|
||||
wrappedHandler = AuthorizationHandler.around(m.getAccMgr(), wrappedHandler);
|
||||
} else {
|
||||
wrappedHandler = httpHandler;
|
||||
}
|
||||
routingHandler.add(method, url, sane(wrappedHandler));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PolicyConfig.PolicyObject> getPolicyObjects(String url) {
|
||||
PolicyConfig policyConfig = m.getConfig().getPolicy();
|
||||
List<PolicyConfig.PolicyObject> policies = new ArrayList<>();
|
||||
if (!policyConfig.getPolicies().isEmpty()) {
|
||||
for (PolicyConfig.PolicyObject policy : policyConfig.getPolicies().values()) {
|
||||
for (Pattern pattern : policy.getPatterns()) {
|
||||
if (pattern.matcher(url).matches()) {
|
||||
policies.add(policy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
private HttpHandler sane(HttpHandler httpHandler) {
|
||||
SaneHandler handler = SaneHandler.around(httpHandler);
|
||||
if (m.getConfig().getLogging().isRequests()) {
|
||||
return new RequestDumpingHandler(handler);
|
||||
} else {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,11 +21,13 @@
|
||||
package io.kamax.mxisd;
|
||||
|
||||
import io.kamax.mxisd.as.AppSvcManager;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.auth.AuthManager;
|
||||
import io.kamax.mxisd.auth.AuthProviders;
|
||||
import io.kamax.mxisd.backend.IdentityStoreSupplier;
|
||||
import io.kamax.mxisd.backend.sql.synapse.Synapse;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.crypto.CryptoFactory;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
@@ -34,6 +36,7 @@ import io.kamax.mxisd.directory.DirectoryManager;
|
||||
import io.kamax.mxisd.directory.DirectoryProviders;
|
||||
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
||||
import io.kamax.mxisd.hash.HashManager;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.ThreePidProviders;
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher;
|
||||
@@ -64,7 +67,7 @@ public class Mxisd {
|
||||
public static final String Version = StringUtils.defaultIfBlank(Mxisd.class.getPackage().getImplementationVersion(), "UNKNOWN");
|
||||
public static final String Agent = Name + "/" + Version;
|
||||
|
||||
private MxisdConfig cfg;
|
||||
private final MxisdConfig cfg;
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
private IRemoteIdentityServerFetcher srvFetcher;
|
||||
@@ -85,6 +88,8 @@ public class Mxisd {
|
||||
private SessionManager sessMgr;
|
||||
private NotificationManager notifMgr;
|
||||
private RegistrationManager regMgr;
|
||||
private AccountManager accMgr;
|
||||
private HashManager hashManager;
|
||||
|
||||
// HS-specific classes
|
||||
private Synapse synapse;
|
||||
@@ -105,7 +110,10 @@ public class Mxisd {
|
||||
IdentityServerUtils.setHttpClient(httpClient);
|
||||
srvFetcher = new RemoteIdentityServerFetcher(httpClient);
|
||||
|
||||
store = new OrmLiteSqlStorage(cfg);
|
||||
StorageConfig.BackendEnum storageBackend = cfg.getStorage().getBackend();
|
||||
StorageConfig.Provider storageProvider = cfg.getStorage().getProvider();
|
||||
store = new OrmLiteSqlStorage(storageBackend, storageProvider);
|
||||
|
||||
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
||||
signMgr = CryptoFactory.getSignatureManager(cfg, keyMgr);
|
||||
clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
||||
@@ -115,15 +123,19 @@ public class Mxisd {
|
||||
ServiceLoader.load(IdentityStoreSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
|
||||
ServiceLoader.load(NotificationHandlerSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
|
||||
|
||||
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
||||
hashManager = new HashManager();
|
||||
hashManager.init(cfg.getHashing(), ThreePidProviders.get(), store);
|
||||
|
||||
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager);
|
||||
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
|
||||
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
||||
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, signMgr);
|
||||
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, signMgr);
|
||||
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
|
||||
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
||||
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
||||
regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr);
|
||||
asHander = new AppSvcManager(this);
|
||||
accMgr = new AccountManager(store, resolver, cfg.getAccountConfig(), cfg.getMatrix());
|
||||
}
|
||||
|
||||
public MxisdConfig getConfig() {
|
||||
@@ -194,6 +206,14 @@ public class Mxisd {
|
||||
return synapse;
|
||||
}
|
||||
|
||||
public AccountManager getAccMgr() {
|
||||
return accMgr;
|
||||
}
|
||||
|
||||
public HashManager getHashManager() {
|
||||
return hashManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
build();
|
||||
}
|
||||
|
@@ -44,31 +44,46 @@ public class MxisdStandaloneExec {
|
||||
try {
|
||||
MxisdConfig cfg = null;
|
||||
Iterator<String> argsIt = Arrays.asList(args).iterator();
|
||||
boolean dump = false;
|
||||
boolean exit = false;
|
||||
while (argsIt.hasNext()) {
|
||||
String arg = argsIt.next();
|
||||
if (StringUtils.equalsAny(arg, "-h", "--help", "-?", "--usage")) {
|
||||
System.out.println("Available arguments:" + System.lineSeparator());
|
||||
System.out.println(" -h, --help Show this help message");
|
||||
System.out.println(" --version Print the version then exit");
|
||||
System.out.println(" -c, --config Set the configuration file location");
|
||||
System.out.println(" -v Increase log level (log more info)");
|
||||
System.out.println(" -vv Further increase log level");
|
||||
System.out.println(" ");
|
||||
System.exit(0);
|
||||
} else if (StringUtils.equals(arg, "-v")) {
|
||||
System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "debug");
|
||||
} else if (StringUtils.equals(arg, "-vv")) {
|
||||
System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "trace");
|
||||
} else if (StringUtils.equalsAny(arg, "-c", "--config")) {
|
||||
String cfgFile = argsIt.next();
|
||||
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
||||
} else if (StringUtils.equals("--version", arg)) {
|
||||
System.out.println(Mxisd.Version);
|
||||
System.exit(0);
|
||||
} else {
|
||||
System.err.println("Invalid argument: " + arg);
|
||||
System.err.println("Try '--help' for available arguments");
|
||||
System.exit(1);
|
||||
switch (arg) {
|
||||
case "-h":
|
||||
case "--help":
|
||||
case "-?":
|
||||
case "--usage":
|
||||
System.out.println("Available arguments:" + System.lineSeparator());
|
||||
System.out.println(" -h, --help Show this help message");
|
||||
System.out.println(" --version Print the version then exit");
|
||||
System.out.println(" -c, --config Set the configuration file location");
|
||||
System.out.println(" -v Increase log level (log more info)");
|
||||
System.out.println(" -vv Further increase log level");
|
||||
System.out.println(" --dump Dump the full ma1sd configuration");
|
||||
System.out.println(" --dump-and-exit Dump the full ma1sd configuration and exit");
|
||||
System.out.println(" ");
|
||||
System.exit(0);
|
||||
return;
|
||||
case "-v":
|
||||
System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "debug");
|
||||
break;
|
||||
case "-vv":
|
||||
System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "trace");
|
||||
break;
|
||||
case "-c":
|
||||
case "--config":
|
||||
String cfgFile = argsIt.next();
|
||||
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
||||
break;
|
||||
case "--dump-and-exit":
|
||||
exit = true;
|
||||
case "--dump":
|
||||
dump = true;
|
||||
break;
|
||||
default:
|
||||
System.err.println("Invalid argument: " + arg);
|
||||
System.err.println("Try '--help' for available arguments");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +91,13 @@ public class MxisdStandaloneExec {
|
||||
cfg = YamlConfigLoader.tryLoadFromFile("ma1sd.yaml").orElseGet(MxisdConfig::new);
|
||||
}
|
||||
|
||||
if (dump) {
|
||||
YamlConfigLoader.dumpConfig(cfg);
|
||||
if (exit) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("ma1sd starting");
|
||||
log.info("Version: {}", Mxisd.Version);
|
||||
|
||||
|
@@ -144,7 +144,13 @@ public class MembershipEventProcessor implements EventTypeProcessor {
|
||||
.collect(Collectors.toList());
|
||||
log.info("Found {} email(s) in identity store for {}", tpids.size(), inviteeId);
|
||||
|
||||
for (_ThreePid tpid : tpids) {
|
||||
log.info("Removing duplicates from identity store");
|
||||
List<_ThreePid> uniqueTpids = tpids.stream()
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
log.info("There are {} unique email(s) in identity store for {}", uniqueTpids.size(), inviteeId);
|
||||
|
||||
for (_ThreePid tpid : uniqueTpids) {
|
||||
log.info("Found Email to notify about room invitation: {}", tpid.getAddress());
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
profiler.getDisplayName(sender).ifPresent(name -> properties.put("sender_display_name", name));
|
||||
|
166
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
166
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
@@ -0,0 +1,166 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.AccountConfig;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.exception.NotFoundException;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.matrix.HomeserverVerifier;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountManager.class);
|
||||
|
||||
private final IStorage storage;
|
||||
private final HomeserverFederationResolver resolver;
|
||||
private final AccountConfig accountConfig;
|
||||
private final MatrixConfig matrixConfig;
|
||||
|
||||
public AccountManager(IStorage storage, HomeserverFederationResolver resolver, AccountConfig accountConfig, MatrixConfig matrixConfig) {
|
||||
this.storage = storage;
|
||||
this.resolver = resolver;
|
||||
this.accountConfig = accountConfig;
|
||||
this.matrixConfig = matrixConfig;
|
||||
}
|
||||
|
||||
public String register(OpenIdToken openIdToken) {
|
||||
Objects.requireNonNull(openIdToken.getAccessToken(), "Missing required access_token");
|
||||
Objects.requireNonNull(openIdToken.getTokenType(), "Missing required token type");
|
||||
Objects.requireNonNull(openIdToken.getMatrixServerName(), "Missing required matrix domain");
|
||||
|
||||
LOGGER.info("Registration from the server: {}", openIdToken.getMatrixServerName());
|
||||
String userId = getUserId(openIdToken);
|
||||
LOGGER.info("UserId: {}", userId);
|
||||
|
||||
String token = UUID.randomUUID().toString();
|
||||
AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(),
|
||||
openIdToken.getMatrixServerName(), openIdToken.getExpiresIn(),
|
||||
Instant.now().getEpochSecond(), userId, token);
|
||||
storage.insertToken(account);
|
||||
|
||||
LOGGER.info("User {} registered", userId);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getUserId(OpenIdToken openIdToken) {
|
||||
String matrixServerName = openIdToken.getMatrixServerName();
|
||||
HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(matrixServerName);
|
||||
String homeserverURL = homeserverTarget.getUrl().toString();
|
||||
LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL);
|
||||
HttpGet getUserInfo = new HttpGet(
|
||||
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||
String userId;
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) {
|
||||
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
String content = EntityUtils.toString(response.getEntity());
|
||||
LOGGER.trace("Response: {}", content);
|
||||
JsonObject body = GsonUtil.parseObj(content);
|
||||
userId = GsonUtil.getStringOrThrow(body, "sub");
|
||||
} else {
|
||||
LOGGER.error("Wrong response status: {}", statusCode);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to get user info.", e);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to create a connection to host: " + homeserverURL, e);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
checkMXID(userId);
|
||||
return userId;
|
||||
}
|
||||
|
||||
private void checkMXID(String userId) {
|
||||
MatrixID mxid;
|
||||
try {
|
||||
mxid = MatrixID.asValid(userId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Wrong MXID: " + userId, e);
|
||||
throw new BadRequestException("Wrong MXID");
|
||||
}
|
||||
|
||||
if (getAccountConfig().isAllowOnlyTrustDomains()) {
|
||||
LOGGER.info("Allow registration only for trust domain.");
|
||||
if (getMatrixConfig().getDomain().equals(mxid.getDomain())) {
|
||||
LOGGER.info("Allow user {} to registration", userId);
|
||||
} else {
|
||||
LOGGER.error("Deny user {} to registration", userId);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Allow registration from any server.");
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserId(String token) {
|
||||
return storage.findAccount(token).orElseThrow(NotFoundException::new).getUserId();
|
||||
}
|
||||
|
||||
public AccountDao findAccount(String token) {
|
||||
AccountDao accountDao = storage.findAccount(token).orElse(null);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
if (accountDao != null) {
|
||||
LOGGER.info("Found account for user: {}", accountDao.getUserId());
|
||||
} else {
|
||||
LOGGER.warn("Account not found.");
|
||||
}
|
||||
}
|
||||
return accountDao;
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
String userId = storage.findAccount(token).orElseThrow(InvalidCredentialsException::new).getUserId();
|
||||
LOGGER.info("Logout: {}", userId);
|
||||
deleteAccount(token);
|
||||
}
|
||||
|
||||
public void deleteAccount(String token) {
|
||||
storage.deleteAccepts(token);
|
||||
storage.deleteToken(token);
|
||||
}
|
||||
|
||||
public void acceptTerm(String token, String url) {
|
||||
storage.acceptTerm(token, url);
|
||||
}
|
||||
|
||||
public boolean isTermAccepted(String token, List<PolicyConfig.PolicyObject> policies) {
|
||||
return policies.isEmpty() || storage.isTermAccepted(token, policies);
|
||||
}
|
||||
|
||||
public AccountConfig getAccountConfig() {
|
||||
return accountConfig;
|
||||
}
|
||||
|
||||
public MatrixConfig getMatrixConfig() {
|
||||
return matrixConfig;
|
||||
}
|
||||
}
|
@@ -140,7 +140,7 @@ public class AuthManager {
|
||||
}
|
||||
|
||||
try {
|
||||
MatrixID.asValid(mxId);
|
||||
MatrixID.asAcceptable(mxId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("The returned User ID {} is not a valid Matrix ID. Login might fail at the Homeserver level", mxId);
|
||||
}
|
||||
|
50
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
50
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class OpenIdToken {
|
||||
|
||||
@SerializedName("access_token")
|
||||
private String accessToken;
|
||||
|
||||
@SerializedName("token_type")
|
||||
private String tokenType;
|
||||
|
||||
@SerializedName("matrix_server_name")
|
||||
private String matrixServerName;
|
||||
|
||||
@SerializedName("expires_in")
|
||||
private long expiresIn;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getMatrixServerName() {
|
||||
return matrixServerName;
|
||||
}
|
||||
|
||||
public void setMatrixServerName(String matrixServerName) {
|
||||
this.matrixServerName = matrixServerName;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
}
|
@@ -164,6 +164,30 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
||||
return input.toString();
|
||||
});
|
||||
|
||||
addBulkSuccessMapper(p);
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
if (!cfg.isHashLookup()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Processor<List<ThreePidMapping>> p = new Processor<>();
|
||||
p.withConfig(cfg.getLookup().getBulk());
|
||||
|
||||
addBulkSuccessMapper(p);
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
private void addBulkSuccessMapper(Processor<List<ThreePidMapping>> p) {
|
||||
p.addSuccessMapper(JsonType, output -> {
|
||||
if (StringUtils.isBlank(output)) {
|
||||
return Collections.emptyList();
|
||||
@@ -188,10 +212,5 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
||||
throw new InternalServerError("Invalid user type: " + item.getId().getType());
|
||||
}).collect(Collectors.toList());
|
||||
});
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -162,6 +162,7 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid
|
||||
log.info("No match were found for {}", mxid);
|
||||
return BackendAuthResult.failure();
|
||||
} catch (LdapException | IOException | CursorException e) {
|
||||
log.error("Unable to invoke query request: ", e);
|
||||
throw new InternalServerError(e);
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,10 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider {
|
||||
|
||||
@@ -137,4 +140,65 @@ public class LdapThreePidProvider extends LdapBackend implements IThreePidProvid
|
||||
return mappingsFound;
|
||||
}
|
||||
|
||||
private List<String> getAttributes() {
|
||||
final List<String> attributes = getCfg().getAttribute().getThreepid().values().stream().flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
attributes.add(getUidAtt());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private Optional<String> getAttributeValue(Entry entry, List<String> attributes) {
|
||||
return attributes.stream()
|
||||
.map(attr -> getAttribute(entry, attr))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
List<ThreePidMapping> result = new ArrayList<>();
|
||||
if (!getCfg().getIdentity().isLookup()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
String filter = getCfg().getIdentity().getFilter();
|
||||
|
||||
try (LdapConnection conn = getConn()) {
|
||||
bind(conn);
|
||||
|
||||
log.debug("Query: {}", filter);
|
||||
List<String> attributes = getAttributes();
|
||||
log.debug("Attributes: {}", GsonUtil.build().toJson(attributes));
|
||||
|
||||
for (String baseDN : getBaseDNs()) {
|
||||
log.debug("Base DN: {}", baseDN);
|
||||
|
||||
try (EntryCursor cursor = conn.search(baseDN, filter, SearchScope.SUBTREE, attributes.toArray(new String[0]))) {
|
||||
while (cursor.next()) {
|
||||
Entry entry = cursor.get();
|
||||
log.info("Found possible match, DN: {}", entry.getDn().getName());
|
||||
Optional<String> mxid = getAttribute(entry, getUidAtt());
|
||||
if (!mxid.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> attributeEntry : getCfg().getAttribute().getThreepid().entrySet()) {
|
||||
String medium = attributeEntry.getKey();
|
||||
getAttributeValue(entry, attributeEntry.getValue())
|
||||
.ifPresent(s -> result.add(new ThreePidMapping(medium, s, buildMatrixIdFromUid(mxid.get()))));
|
||||
}
|
||||
}
|
||||
} catch (CursorLdapReferralException e) {
|
||||
log.warn("3PID is only available via referral, skipping", e);
|
||||
} catch (IOException | LdapException | CursorException e) {
|
||||
log.error("Unable to fetch 3PID mappings", e);
|
||||
}
|
||||
}
|
||||
} catch (LdapException | IOException e) {
|
||||
log.error("Unable to fetch 3PID mappings", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -48,6 +48,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProvider, IThreePidProvider, ProfileProvider {
|
||||
|
||||
@@ -171,4 +172,15 @@ public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProv
|
||||
}).orElseGet(BackendAuthResult::failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
if (!cfg.isHashEnabled()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return cfg.getIdentities().stream()
|
||||
.map(mic -> mic.getThreepids().stream().map(mtp -> new ThreePidMapping(mtp.getMedium(), mtp.getAddress(), mic.getUsername())))
|
||||
.flatMap(s -> s).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -104,4 +105,29 @@ public abstract class SqlThreePidProvider implements IThreePidProvider {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
String query = cfg.getLookup().getQuery();
|
||||
if (StringUtils.isBlank(query)) {
|
||||
log.warn("Lookup query not configured, skip.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
log.debug("Uses query to match users: {}", query);
|
||||
List<ThreePidMapping> result = new ArrayList<>();
|
||||
try (Connection connection = pool.get()) {
|
||||
PreparedStatement statement = connection.prepareStatement(query);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
while (resultSet.next()) {
|
||||
String mxid = resultSet.getString("mxid");
|
||||
String medium = resultSet.getString("medium");
|
||||
String address = resultSet.getString("address");
|
||||
result.add(new ThreePidMapping(medium, address, mxid));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -29,23 +29,27 @@ import java.util.Optional;
|
||||
|
||||
public class Synapse {
|
||||
|
||||
private SqlConnectionPool pool;
|
||||
private final SqlConnectionPool pool;
|
||||
private final SynapseSqlProviderConfig providerConfig;
|
||||
|
||||
public Synapse(SynapseSqlProviderConfig sqlCfg) {
|
||||
this.pool = new SqlConnectionPool(sqlCfg);
|
||||
providerConfig = sqlCfg;
|
||||
}
|
||||
|
||||
public Optional<String> getRoomName(String id) {
|
||||
return pool.withConnFunction(conn -> {
|
||||
PreparedStatement stmt = conn.prepareStatement(SynapseQueries.getRoomName());
|
||||
stmt.setString(1, id);
|
||||
ResultSet rSet = stmt.executeQuery();
|
||||
if (!rSet.next()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String query = providerConfig.isLegacyRoomNames() ? SynapseQueries.getLegacyRoomName() : SynapseQueries.getRoomName();
|
||||
|
||||
return Optional.ofNullable(rSet.getString(1));
|
||||
return pool.withConnFunction(conn -> {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
stmt.setString(1, id);
|
||||
ResultSet rSet = stmt.executeQuery();
|
||||
if (!rSet.next()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(rSet.getString(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ public class SynapseQueries {
|
||||
if (StringUtils.equals("sqlite", type)) {
|
||||
return "select " + getUserId(type, domain) + ", displayname from profiles p where displayname like ?";
|
||||
} else if (StringUtils.equals("postgresql", type)) {
|
||||
return "select " + getUserId(type, domain) + ", displayname from profiles p where displayname ilike ?";
|
||||
return "SELECT u.name,p.displayname FROM users u JOIN profiles p ON u.name LIKE concat('@',p.user_id,':%') WHERE u.is_guest = 0 AND u.appservice_id IS NULL AND p.displayname LIKE ?";
|
||||
} else {
|
||||
throw new ConfigurationException("Invalid Synapse SQL type: " + type);
|
||||
}
|
||||
@@ -72,7 +72,10 @@ public class SynapseQueries {
|
||||
}
|
||||
|
||||
public static String getRoomName() {
|
||||
return "select r.name from room_names r, events e, (select r1.room_id,max(e1.origin_server_ts) ts from room_names r1, events e1 where r1.event_id = e1.event_id group by r1.room_id) rle where e.origin_server_ts = rle.ts and r.event_id = e.event_id and r.room_id = ?";
|
||||
return "select name from room_stats_state where room_id = ? limit 1";
|
||||
}
|
||||
|
||||
public static String getLegacyRoomName() {
|
||||
return "select r.name from room_names r, events e, (select r1.room_id,max(e1.origin_server_ts) ts from room_names r1, events e1 where r1.event_id = e1.event_id group by r1.room_id) rle where e.origin_server_ts = rle.ts and r.event_id = e.event_id and r.room_id = ?";
|
||||
}
|
||||
}
|
||||
|
8
src/main/java/io/kamax/mxisd/config/AcceptingPolicy.java
Normal file
8
src/main/java/io/kamax/mxisd/config/AcceptingPolicy.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public enum AcceptingPolicy {
|
||||
|
||||
ALL,
|
||||
|
||||
ANY
|
||||
}
|
24
src/main/java/io/kamax/mxisd/config/AccountConfig.java
Normal file
24
src/main/java/io/kamax/mxisd/config/AccountConfig.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountConfig {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(DirectoryConfig.class);
|
||||
|
||||
private boolean allowOnlyTrustDomains = true;
|
||||
|
||||
public boolean isAllowOnlyTrustDomains() {
|
||||
return allowOnlyTrustDomains;
|
||||
}
|
||||
|
||||
public void setAllowOnlyTrustDomains(boolean allowOnlyTrustDomains) {
|
||||
this.allowOnlyTrustDomains = allowOnlyTrustDomains;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Account config ---");
|
||||
log.info("Allow registration only for trust domain: {}", isAllowOnlyTrustDomains());
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public interface DatabaseStorageConfig {
|
||||
String getDatabase();
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public class DurationDeserializer {
|
||||
|
||||
public long deserialize(String argument) {
|
||||
long duration = 0L;
|
||||
for (String part : argument.split(" ")) {
|
||||
String unit = part.substring(part.length() - 1);
|
||||
long value = Long.parseLong(part.substring(0, part.length() - 1));
|
||||
switch (unit) {
|
||||
case "s":
|
||||
duration += value;
|
||||
break;
|
||||
case "m":
|
||||
duration += value * 60;
|
||||
break;
|
||||
case "h":
|
||||
duration += value * 60 * 60;
|
||||
break;
|
||||
case "d":
|
||||
duration += value * 60 * 60 * 24;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unknown duration unit: %s", unit));
|
||||
}
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
}
|
@@ -309,6 +309,7 @@ public class ExecConfig {
|
||||
private Boolean enabled;
|
||||
private int priority;
|
||||
private Lookup lookup = new Lookup();
|
||||
private boolean hashLookup = false;
|
||||
|
||||
public Boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -334,6 +335,13 @@ public class ExecConfig {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
|
||||
public boolean isHashLookup() {
|
||||
return hashLookup;
|
||||
}
|
||||
|
||||
public void setHashLookup(boolean hashLookup) {
|
||||
this.hashLookup = hashLookup;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Profile {
|
||||
|
125
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
125
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class HashingConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashingConfig.class);
|
||||
|
||||
private boolean enabled = false;
|
||||
private int pepperLength = 20;
|
||||
private RotationPolicyEnum rotationPolicy;
|
||||
private HashStorageEnum hashStorageType = HashStorageEnum.in_memory;
|
||||
private String delay = "10s";
|
||||
private transient long delayInSeconds = 10;
|
||||
private int requests = 10;
|
||||
private List<Algorithm> algorithms = new ArrayList<>();
|
||||
|
||||
public void build(MatrixConfig matrixConfig) {
|
||||
if (isEnabled()) {
|
||||
LOGGER.info("--- Hash configuration ---");
|
||||
LOGGER.info(" Pepper length: {}", getPepperLength());
|
||||
LOGGER.info(" Rotation policy: {}", getRotationPolicy());
|
||||
LOGGER.info(" Hash storage type: {}", getHashStorageType());
|
||||
Objects.requireNonNull(getHashStorageType(), "Storage type must be specified");
|
||||
if (RotationPolicyEnum.per_seconds == getRotationPolicy()) {
|
||||
setDelayInSeconds(new DurationDeserializer().deserialize(getDelay()));
|
||||
LOGGER.info(" Rotation delay: {}", getDelay());
|
||||
LOGGER.info(" Rotation delay in seconds: {}", getDelayInSeconds());
|
||||
}
|
||||
if (RotationPolicyEnum.per_requests == getRotationPolicy()) {
|
||||
LOGGER.info(" Rotation after requests: {}", getRequests());
|
||||
}
|
||||
LOGGER.info(" Algorithms: {}", getAlgorithms());
|
||||
} else {
|
||||
if (matrixConfig.isV2()) {
|
||||
LOGGER.warn("V2 enabled without the hash configuration.");
|
||||
}
|
||||
LOGGER.info("Hash configuration disabled, used only `none` pepper.");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Algorithm {
|
||||
none,
|
||||
sha256
|
||||
}
|
||||
|
||||
public enum RotationPolicyEnum {
|
||||
per_requests,
|
||||
per_seconds
|
||||
}
|
||||
|
||||
public enum HashStorageEnum {
|
||||
in_memory,
|
||||
sql
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public int getPepperLength() {
|
||||
return pepperLength;
|
||||
}
|
||||
|
||||
public void setPepperLength(int pepperLength) {
|
||||
this.pepperLength = pepperLength;
|
||||
}
|
||||
|
||||
public RotationPolicyEnum getRotationPolicy() {
|
||||
return rotationPolicy;
|
||||
}
|
||||
|
||||
public void setRotationPolicy(RotationPolicyEnum rotationPolicy) {
|
||||
this.rotationPolicy = rotationPolicy;
|
||||
}
|
||||
|
||||
public HashStorageEnum getHashStorageType() {
|
||||
return hashStorageType;
|
||||
}
|
||||
|
||||
public void setHashStorageType(HashStorageEnum hashStorageType) {
|
||||
this.hashStorageType = hashStorageType;
|
||||
}
|
||||
|
||||
public String getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public void setDelay(String delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public long getDelayInSeconds() {
|
||||
return delayInSeconds;
|
||||
}
|
||||
|
||||
public void setDelayInSeconds(long delayInSeconds) {
|
||||
this.delayInSeconds = delayInSeconds;
|
||||
}
|
||||
|
||||
public int getRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
public void setRequests(int requests) {
|
||||
this.requests = requests;
|
||||
}
|
||||
|
||||
public List<Algorithm> getAlgorithms() {
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
public void setAlgorithms(List<Algorithm> algorithms) {
|
||||
this.algorithms = algorithms;
|
||||
}
|
||||
}
|
60
src/main/java/io/kamax/mxisd/config/LoggingConfig.java
Normal file
60
src/main/java/io/kamax/mxisd/config/LoggingConfig.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LoggingConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("App");
|
||||
|
||||
private String root;
|
||||
private String app;
|
||||
private boolean requests = false;
|
||||
|
||||
public String getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public void setRoot(String root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public boolean isRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
public void setRequests(boolean requests) {
|
||||
this.requests = requests;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
LOGGER.info("Logging config:");
|
||||
if (StringUtils.isNotBlank(getRoot())) {
|
||||
LOGGER.info(" Default log level: {}", getRoot());
|
||||
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", getRoot());
|
||||
}
|
||||
|
||||
String appLevel = System.getProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd");
|
||||
if (StringUtils.isNotBlank(appLevel)) {
|
||||
LOGGER.info(" Logging level set by environment: {}", appLevel);
|
||||
} else if (StringUtils.isNotBlank(getApp())) {
|
||||
System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", getApp());
|
||||
LOGGER.info(" Logging level set by the configuration: {}", getApp());
|
||||
} else {
|
||||
LOGGER.info(" Logging level hasn't set, use default");
|
||||
}
|
||||
LOGGER.info(" Log requests: {}", isRequests());
|
||||
if (isRequests()) {
|
||||
LOGGER.warn(" Request dumping enabled, use this only to debug purposes, don't use it in the production.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -63,6 +63,8 @@ public class MatrixConfig {
|
||||
|
||||
private String domain;
|
||||
private Identity identity = new Identity();
|
||||
private boolean v1 = true;
|
||||
private boolean v2 = false;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
@@ -80,6 +82,22 @@ public class MatrixConfig {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
public boolean isV1() {
|
||||
return v1;
|
||||
}
|
||||
|
||||
public void setV1(boolean v1) {
|
||||
this.v1 = v1;
|
||||
}
|
||||
|
||||
public boolean isV2() {
|
||||
return v2;
|
||||
}
|
||||
|
||||
public void setV2(boolean v2) {
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Matrix config ---");
|
||||
|
||||
@@ -90,6 +108,11 @@ public class MatrixConfig {
|
||||
log.info("Domain: {}", getDomain());
|
||||
log.info("Identity:");
|
||||
log.info("\tServers: {}", GsonUtil.get().toJson(identity.getServers()));
|
||||
log.info("API v1: {}", v1);
|
||||
log.info("API v2: {}", v2);
|
||||
if (v1) {
|
||||
log.warn("API v1 is deprecated via MSC2140: https://github.com/matrix-org/matrix-doc/pull/2140 and will be deleted in future releases.");
|
||||
log.warn("Please upgrade your homeserver and enable only API v2.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -92,6 +92,7 @@ public class MxisdConfig {
|
||||
private AppServiceConfig appsvc = new AppServiceConfig();
|
||||
private AuthenticationConfig auth = new AuthenticationConfig();
|
||||
private DirectoryConfig directory = new DirectoryConfig();
|
||||
private AccountConfig accountConfig = new AccountConfig();
|
||||
private Dns dns = new Dns();
|
||||
private ExecConfig exec = new ExecConfig();
|
||||
private FirebaseConfig firebase = new FirebaseConfig();
|
||||
@@ -114,6 +115,9 @@ public class MxisdConfig {
|
||||
private ThreePidConfig threepid = new ThreePidConfig();
|
||||
private ViewConfig view = new ViewConfig();
|
||||
private WordpressConfig wordpress = new WordpressConfig();
|
||||
private PolicyConfig policy = new PolicyConfig();
|
||||
private HashingConfig hashing = new HashingConfig();
|
||||
private LoggingConfig logging = new LoggingConfig();
|
||||
|
||||
public AppServiceConfig getAppsvc() {
|
||||
return appsvc;
|
||||
@@ -131,6 +135,14 @@ public class MxisdConfig {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public AccountConfig getAccountConfig() {
|
||||
return accountConfig;
|
||||
}
|
||||
|
||||
public void setAccountConfig(AccountConfig accountConfig) {
|
||||
this.accountConfig = accountConfig;
|
||||
}
|
||||
|
||||
public DirectoryConfig getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
@@ -315,6 +327,30 @@ public class MxisdConfig {
|
||||
this.wordpress = wordpress;
|
||||
}
|
||||
|
||||
public PolicyConfig getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void setPolicy(PolicyConfig policy) {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
public HashingConfig getHashing() {
|
||||
return hashing;
|
||||
}
|
||||
|
||||
public void setHashing(HashingConfig hashing) {
|
||||
this.hashing = hashing;
|
||||
}
|
||||
|
||||
public LoggingConfig getLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
public void setLogging(LoggingConfig logging) {
|
||||
this.logging = logging;
|
||||
}
|
||||
|
||||
public MxisdConfig inMemory() {
|
||||
getKey().setPath(":memory:");
|
||||
getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||
@@ -323,6 +359,8 @@ public class MxisdConfig {
|
||||
}
|
||||
|
||||
public MxisdConfig build() {
|
||||
getLogging().build();
|
||||
|
||||
if (StringUtils.isBlank(getServer().getName())) {
|
||||
getServer().setName(getMatrix().getDomain());
|
||||
log.debug("server.name is empty, using matrix.domain");
|
||||
@@ -330,7 +368,9 @@ public class MxisdConfig {
|
||||
|
||||
getAppsvc().build();
|
||||
getAuth().build();
|
||||
getAccountConfig().build();
|
||||
getDirectory().build();
|
||||
getDns().build();
|
||||
getExec().build();
|
||||
getFirebase().build();
|
||||
getForward().build();
|
||||
@@ -352,6 +392,8 @@ public class MxisdConfig {
|
||||
getThreepid().build();
|
||||
getView().build();
|
||||
getWordpress().build();
|
||||
getPolicy().build();
|
||||
getHashing().build(getMatrix());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
114
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
114
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class PolicyConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PolicyConfig.class);
|
||||
|
||||
private Map<String, PolicyObject> policies = new HashMap<>();
|
||||
|
||||
public static class TermObject {
|
||||
|
||||
private String name;
|
||||
|
||||
private String url;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PolicyObject {
|
||||
|
||||
private String version;
|
||||
|
||||
private Map<String, TermObject> terms;
|
||||
|
||||
private List<String> regexp = new ArrayList<>();
|
||||
|
||||
private transient List<Pattern> patterns = new ArrayList<>();
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Map<String, TermObject> getTerms() {
|
||||
return terms;
|
||||
}
|
||||
|
||||
public void setTerms(Map<String, TermObject> terms) {
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
public List<String> getRegexp() {
|
||||
return regexp;
|
||||
}
|
||||
|
||||
public void setRegexp(List<String> regexp) {
|
||||
this.regexp = regexp;
|
||||
}
|
||||
|
||||
public List<Pattern> getPatterns() {
|
||||
return patterns;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, PolicyObject> getPolicies() {
|
||||
return policies;
|
||||
}
|
||||
|
||||
public void setPolicies(Map<String, PolicyObject> policies) {
|
||||
this.policies = policies;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
LOGGER.info("--- Policy Config ---");
|
||||
if (getPolicies().isEmpty()) {
|
||||
LOGGER.info("Empty");
|
||||
} else {
|
||||
for (Map.Entry<String, PolicyObject> policyObjectItem : getPolicies().entrySet()) {
|
||||
PolicyObject policyObject = policyObjectItem.getValue();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Policy \"").append(policyObjectItem.getKey()).append("\"\n");
|
||||
sb.append(" version: ").append(policyObject.getVersion()).append("\n");
|
||||
for (String regexp : policyObjectItem.getValue().getRegexp()) {
|
||||
sb.append(" - ").append(regexp).append("\n");
|
||||
policyObjectItem.getValue().getPatterns().add(Pattern.compile(regexp));
|
||||
}
|
||||
sb.append(" terms:\n");
|
||||
if (policyObject.getTerms() != null) {
|
||||
for (Map.Entry<String, TermObject> termItem : policyObject.getTerms().entrySet()) {
|
||||
sb.append(" - lang: ").append(termItem.getKey()).append("\n");
|
||||
sb.append(" name: ").append(termItem.getValue().getName()).append("\n");
|
||||
sb.append(" url: ").append(termItem.getValue().getUrl()).append("\n");
|
||||
}
|
||||
}
|
||||
LOGGER.info(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
105
src/main/java/io/kamax/mxisd/config/PostgresqlStorageConfig.java
Normal file
105
src/main/java/io/kamax/mxisd/config/PostgresqlStorageConfig.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public class PostgresqlStorageConfig implements DatabaseStorageConfig {
|
||||
|
||||
private String database;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private boolean pool;
|
||||
|
||||
private int maxConnectionsFree = 1;
|
||||
|
||||
private long maxConnectionAgeMillis = 60 * 60 * 1000;
|
||||
|
||||
private long checkConnectionsEveryMillis = 30 * 1000;
|
||||
|
||||
private boolean testBeforeGetFromPool = false;
|
||||
|
||||
@Override
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public void setDatabase(String database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
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 boolean isPool() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
public void setPool(boolean pool) {
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
public int getMaxConnectionsFree() {
|
||||
return maxConnectionsFree;
|
||||
}
|
||||
|
||||
public void setMaxConnectionsFree(int maxConnectionsFree) {
|
||||
this.maxConnectionsFree = maxConnectionsFree;
|
||||
}
|
||||
|
||||
public long getMaxConnectionAgeMillis() {
|
||||
return maxConnectionAgeMillis;
|
||||
}
|
||||
|
||||
public void setMaxConnectionAgeMillis(long maxConnectionAgeMillis) {
|
||||
this.maxConnectionAgeMillis = maxConnectionAgeMillis;
|
||||
}
|
||||
|
||||
public long getCheckConnectionsEveryMillis() {
|
||||
return checkConnectionsEveryMillis;
|
||||
}
|
||||
|
||||
public void setCheckConnectionsEveryMillis(long checkConnectionsEveryMillis) {
|
||||
this.checkConnectionsEveryMillis = checkConnectionsEveryMillis;
|
||||
}
|
||||
|
||||
public boolean isTestBeforeGetFromPool() {
|
||||
return testBeforeGetFromPool;
|
||||
}
|
||||
|
||||
public void setTestBeforeGetFromPool(boolean testBeforeGetFromPool) {
|
||||
this.testBeforeGetFromPool = testBeforeGetFromPool;
|
||||
}
|
||||
}
|
@@ -20,10 +20,11 @@
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public class SQLiteStorageConfig {
|
||||
public class SQLiteStorageConfig implements DatabaseStorageConfig {
|
||||
|
||||
private String database;
|
||||
|
||||
@Override
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ public class ServerConfig {
|
||||
private String name;
|
||||
private int port = 8090;
|
||||
private String publicUrl;
|
||||
private String hostname;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -59,6 +60,14 @@ public class ServerConfig {
|
||||
this.publicUrl = publicUrl;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Server config ---");
|
||||
|
||||
@@ -75,9 +84,13 @@ public class ServerConfig {
|
||||
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"));
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(getHostname())) {
|
||||
setHostname("0.0.0.0");
|
||||
}
|
||||
|
||||
log.info("Name: {}", getName());
|
||||
log.info("Port: {}", getPort());
|
||||
log.info("Public URL: {}", getPublicUrl());
|
||||
log.info("Hostname: {}", getHostname());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -47,6 +47,8 @@ public class SessionConfig {
|
||||
public static class PolicyUnbind {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private boolean notifications = true;
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
@@ -55,11 +57,20 @@ public class SessionConfig {
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean shouldNotify() {
|
||||
return notifications;
|
||||
}
|
||||
|
||||
public void setNotifications(boolean notifications) {
|
||||
this.notifications = notifications;
|
||||
}
|
||||
}
|
||||
|
||||
public Policy() {
|
||||
validation.enabled = true;
|
||||
unbind.enabled = true;
|
||||
unbind.notifications = true;
|
||||
}
|
||||
|
||||
private PolicyTemplate validation = new PolicyTemplate();
|
||||
|
@@ -21,14 +21,21 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
public class StorageConfig {
|
||||
|
||||
public enum BackendEnum {
|
||||
sqlite,
|
||||
|
||||
postgresql
|
||||
}
|
||||
|
||||
public static class Provider {
|
||||
|
||||
private SQLiteStorageConfig sqlite = new SQLiteStorageConfig();
|
||||
|
||||
private PostgresqlStorageConfig postgresql = new PostgresqlStorageConfig();
|
||||
|
||||
public SQLiteStorageConfig getSqlite() {
|
||||
return sqlite;
|
||||
}
|
||||
@@ -37,16 +44,23 @@ public class StorageConfig {
|
||||
this.sqlite = sqlite;
|
||||
}
|
||||
|
||||
public PostgresqlStorageConfig getPostgresql() {
|
||||
return postgresql;
|
||||
}
|
||||
|
||||
public void setPostgresql(PostgresqlStorageConfig postgresql) {
|
||||
this.postgresql = postgresql;
|
||||
}
|
||||
}
|
||||
|
||||
private String backend = "sqlite";
|
||||
private BackendEnum backend = BackendEnum.sqlite; // or postgresql
|
||||
private Provider provider = new Provider();
|
||||
|
||||
public String getBackend() {
|
||||
public BackendEnum getBackend() {
|
||||
return backend;
|
||||
}
|
||||
|
||||
public void setBackend(String backend) {
|
||||
public void setBackend(BackendEnum backend) {
|
||||
this.backend = backend;
|
||||
}
|
||||
|
||||
@@ -59,7 +73,7 @@ public class StorageConfig {
|
||||
}
|
||||
|
||||
public void build() {
|
||||
if (StringUtils.isBlank(getBackend())) {
|
||||
if (getBackend() == null) {
|
||||
throw new ConfigurationException("storage.backend");
|
||||
}
|
||||
}
|
||||
|
@@ -76,4 +76,14 @@ public class YamlConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
public static void dumpConfig(MxisdConfig cfg) {
|
||||
Representer rep = new Representer();
|
||||
rep.getPropertyUtils().setBeanAccess(BeanAccess.FIELD);
|
||||
rep.getPropertyUtils().setAllowReadOnlyProperties(true);
|
||||
rep.getPropertyUtils().setSkipMissingProperties(true);
|
||||
|
||||
Yaml yaml = new Yaml(new Constructor(MxisdConfig.class), rep);
|
||||
String dump = yaml.dump(cfg);
|
||||
log.info("Full configuration:\n{}", dump);
|
||||
}
|
||||
}
|
||||
|
@@ -233,6 +233,7 @@ public abstract class LdapConfig {
|
||||
private String filter;
|
||||
private String token = "%3pid";
|
||||
private Map<String, String> medium = new HashMap<>();
|
||||
private boolean lookup = false;
|
||||
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
@@ -262,6 +263,13 @@ public abstract class LdapConfig {
|
||||
this.medium = medium;
|
||||
}
|
||||
|
||||
public boolean isLookup() {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
public void setLookup(boolean lookup) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Profile {
|
||||
|
@@ -27,6 +27,7 @@ public class MemoryStoreConfig {
|
||||
|
||||
private boolean enabled;
|
||||
private List<MemoryIdentityConfig> identities = new ArrayList<>();
|
||||
private boolean hashEnabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -44,6 +45,14 @@ public class MemoryStoreConfig {
|
||||
this.identities = identities;
|
||||
}
|
||||
|
||||
public boolean isHashEnabled() {
|
||||
return hashEnabled;
|
||||
}
|
||||
|
||||
public void setHashEnabled(boolean hashEnabled) {
|
||||
this.hashEnabled = hashEnabled;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
// no-op
|
||||
}
|
||||
|
@@ -45,4 +45,21 @@ public class MemoryThreePid implements _ThreePid {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
MemoryThreePid threePid = (MemoryThreePid) 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;
|
||||
}
|
||||
}
|
||||
|
@@ -124,11 +124,23 @@ public abstract class SqlConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class Lookup {
|
||||
private String query = "SELECT user_id AS mxid, medium, address from user_threepid_id_server";
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Identity {
|
||||
|
||||
private Boolean enabled;
|
||||
private String type = "mxid";
|
||||
private String query = "SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?";
|
||||
private String query = "SELECT user_id AS uid FROM user_threepid_id_server WHERE medium = ? AND address = ?";
|
||||
private Map<String, String> medium = new HashMap<>();
|
||||
|
||||
public Boolean isEnabled() {
|
||||
@@ -264,6 +276,7 @@ public abstract class SqlConfig {
|
||||
private Directory directory = new Directory();
|
||||
private Identity identity = new Identity();
|
||||
private Profile profile = new Profile();
|
||||
private Lookup lookup = new Lookup();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -321,6 +334,14 @@ public abstract class SqlConfig {
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public Lookup getLookup() {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
public void setLookup(Lookup lookup) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
|
||||
protected abstract String getProviderName();
|
||||
|
||||
public void build() {
|
||||
@@ -354,6 +375,7 @@ public abstract class SqlConfig {
|
||||
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("Lookup query: {}", getLookup().getQuery());
|
||||
log.info("Profile:");
|
||||
log.info(" Enabled: {}", getProfile().isEnabled());
|
||||
if (getProfile().isEnabled()) {
|
||||
|
@@ -24,9 +24,23 @@ import io.kamax.mxisd.UserIdType;
|
||||
import io.kamax.mxisd.backend.sql.synapse.SynapseQueries;
|
||||
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SynapseSqlProviderConfig extends SqlConfig {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SynapseSqlProviderConfig.class);
|
||||
|
||||
private boolean legacyRoomNames = false;
|
||||
|
||||
public boolean isLegacyRoomNames() {
|
||||
return legacyRoomNames;
|
||||
}
|
||||
|
||||
public void setLegacyRoomNames(boolean legacyRoomNames) {
|
||||
this.legacyRoomNames = legacyRoomNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProviderName() {
|
||||
return "Synapse SQL";
|
||||
@@ -42,7 +56,7 @@ public class SynapseSqlProviderConfig extends SqlConfig {
|
||||
|
||||
if (getIdentity().isEnabled() && StringUtils.isBlank(getIdentity().getType())) {
|
||||
getIdentity().setType("mxid");
|
||||
getIdentity().setQuery("SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?");
|
||||
getIdentity().setQuery("SELECT user_id AS uid FROM user_threepid_id_server WHERE medium = ? AND address = ?");
|
||||
}
|
||||
|
||||
if (getProfile().isEnabled()) {
|
||||
@@ -65,4 +79,12 @@ public class SynapseSqlProviderConfig extends SqlConfig {
|
||||
printConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void printConfig() {
|
||||
super.printConfig();
|
||||
|
||||
if (isEnabled()) {
|
||||
log.info("Use legacy room name query: {}", isLegacyRoomNames());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
public class InvalidParamException extends RuntimeException {
|
||||
|
||||
public InvalidParamException() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
public class InvalidPepperException extends RuntimeException {
|
||||
|
||||
public InvalidPepperException() {
|
||||
}
|
||||
}
|
94
src/main/java/io/kamax/mxisd/hash/HashManager.java
Normal file
94
src/main/java/io/kamax/mxisd/hash/HashManager.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package io.kamax.mxisd.hash;
|
||||
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
import io.kamax.mxisd.hash.engine.Engine;
|
||||
import io.kamax.mxisd.hash.engine.HashEngine;
|
||||
import io.kamax.mxisd.hash.engine.NoneEngine;
|
||||
import io.kamax.mxisd.hash.rotation.HashRotationStrategy;
|
||||
import io.kamax.mxisd.hash.rotation.NoOpRotationStrategy;
|
||||
import io.kamax.mxisd.hash.rotation.RotationPerRequests;
|
||||
import io.kamax.mxisd.hash.rotation.TimeBasedRotation;
|
||||
import io.kamax.mxisd.hash.storage.EmptyStorage;
|
||||
import io.kamax.mxisd.hash.storage.HashStorage;
|
||||
import io.kamax.mxisd.hash.storage.InMemoryHashStorage;
|
||||
import io.kamax.mxisd.hash.storage.SqlHashStorage;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class HashManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashManager.class);
|
||||
|
||||
private Engine engine;
|
||||
private HashRotationStrategy rotationStrategy;
|
||||
private HashStorage hashStorage;
|
||||
private HashingConfig config;
|
||||
private IStorage storage;
|
||||
private AtomicBoolean configured = new AtomicBoolean(false);
|
||||
|
||||
public void init(HashingConfig config, List<? extends IThreePidProvider> providers, IStorage storage) {
|
||||
this.config = config;
|
||||
this.storage = storage;
|
||||
initStorage();
|
||||
engine = config.isEnabled() ? new HashEngine(providers, getHashStorage(), config) : new NoneEngine();
|
||||
initRotationStrategy();
|
||||
configured.set(true);
|
||||
}
|
||||
|
||||
private void initStorage() {
|
||||
if (config.isEnabled()) {
|
||||
switch (config.getHashStorageType()) {
|
||||
case in_memory:
|
||||
this.hashStorage = new InMemoryHashStorage();
|
||||
break;
|
||||
case sql:
|
||||
this.hashStorage = new SqlHashStorage(storage);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown storage type: " + config.getHashStorageType());
|
||||
}
|
||||
} else {
|
||||
this.hashStorage = new EmptyStorage();
|
||||
}
|
||||
}
|
||||
|
||||
private void initRotationStrategy() {
|
||||
if (config.isEnabled()) {
|
||||
switch (config.getRotationPolicy()) {
|
||||
case per_requests:
|
||||
this.rotationStrategy = new RotationPerRequests(config.getRequests());
|
||||
break;
|
||||
case per_seconds:
|
||||
this.rotationStrategy = new TimeBasedRotation(config.getDelayInSeconds());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown rotation type: " + config.getHashStorageType());
|
||||
}
|
||||
} else {
|
||||
this.rotationStrategy = new NoOpRotationStrategy();
|
||||
}
|
||||
|
||||
this.rotationStrategy.register(getHashEngine());
|
||||
}
|
||||
|
||||
public Engine getHashEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
public HashRotationStrategy getRotationStrategy() {
|
||||
return rotationStrategy;
|
||||
}
|
||||
|
||||
public HashStorage getHashStorage() {
|
||||
return hashStorage;
|
||||
}
|
||||
|
||||
public HashingConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
7
src/main/java/io/kamax/mxisd/hash/engine/Engine.java
Normal file
7
src/main/java/io/kamax/mxisd/hash/engine/Engine.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package io.kamax.mxisd.hash.engine;
|
||||
|
||||
public interface Engine {
|
||||
void updateHashes();
|
||||
|
||||
String getPepper();
|
||||
}
|
66
src/main/java/io/kamax/mxisd/hash/engine/HashEngine.java
Normal file
66
src/main/java/io/kamax/mxisd/hash/engine/HashEngine.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package io.kamax.mxisd.hash.engine;
|
||||
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
import io.kamax.mxisd.hash.storage.HashStorage;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
public class HashEngine implements Engine {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashEngine.class);
|
||||
|
||||
private final List<? extends IThreePidProvider> providers;
|
||||
private final HashStorage hashStorage;
|
||||
private final HashingConfig config;
|
||||
private final Base64.Encoder base64 = Base64.getUrlEncoder().withoutPadding();
|
||||
private String pepper;
|
||||
|
||||
public HashEngine(List<? extends IThreePidProvider> providers, HashStorage hashStorage, HashingConfig config) {
|
||||
this.providers = providers;
|
||||
this.hashStorage = hashStorage;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHashes() {
|
||||
LOGGER.info("Start update hashes.");
|
||||
synchronized (hashStorage) {
|
||||
this.pepper = newPepper();
|
||||
hashStorage.clear();
|
||||
for (IThreePidProvider provider : providers) {
|
||||
try {
|
||||
LOGGER.info("Populate hashes from the handler: {}", provider.getClass().getCanonicalName());
|
||||
for (ThreePidMapping pidMapping : provider.populateHashes()) {
|
||||
LOGGER.debug("Found 3PID: {}", pidMapping);
|
||||
hashStorage.add(pidMapping, hash(pidMapping));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to update hashes of the provider: " + provider.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.info("Finish update hashes.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPepper() {
|
||||
synchronized (hashStorage) {
|
||||
return pepper;
|
||||
}
|
||||
}
|
||||
|
||||
protected String hash(ThreePidMapping pidMapping) {
|
||||
return base64.encodeToString(DigestUtils.sha256(pidMapping.getValue() + " " + pidMapping.getMedium() + " " + getPepper()));
|
||||
}
|
||||
|
||||
protected String newPepper() {
|
||||
return RandomStringUtils.random(config.getPepperLength(), true, true);
|
||||
}
|
||||
}
|
19
src/main/java/io/kamax/mxisd/hash/engine/NoneEngine.java
Normal file
19
src/main/java/io/kamax/mxisd/hash/engine/NoneEngine.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package io.kamax.mxisd.hash.engine;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class NoneEngine implements Engine {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NoneEngine.class);
|
||||
|
||||
@Override
|
||||
public void updateHashes() {
|
||||
LOGGER.info("Nothing to update.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPepper() {
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.engine.Engine;
|
||||
|
||||
public interface HashRotationStrategy {
|
||||
|
||||
void register(Engine engine);
|
||||
|
||||
Engine getHashEngine();
|
||||
|
||||
void newRequest();
|
||||
|
||||
default void trigger() {
|
||||
getHashEngine().updateHashes();
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.engine.Engine;
|
||||
|
||||
public class NoOpRotationStrategy implements HashRotationStrategy {
|
||||
|
||||
private Engine engine;
|
||||
|
||||
@Override
|
||||
public void register(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getHashEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRequest() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.engine.Engine;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class RotationPerRequests implements HashRotationStrategy {
|
||||
|
||||
private Engine engine;
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
private final int barrier;
|
||||
|
||||
public RotationPerRequests(int barrier) {
|
||||
this.barrier = barrier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Engine engine) {
|
||||
this.engine = engine;
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getHashEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void newRequest() {
|
||||
int newValue = counter.incrementAndGet();
|
||||
if (newValue >= barrier) {
|
||||
counter.set(0);
|
||||
trigger();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.engine.Engine;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeBasedRotation implements HashRotationStrategy {
|
||||
|
||||
private final long delay;
|
||||
private Engine engine;
|
||||
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public TimeBasedRotation(long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Engine engine) {
|
||||
this.engine = engine;
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdown));
|
||||
executorService.scheduleWithFixedDelay(this::trigger, 0, delay, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getHashEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRequest() {
|
||||
}
|
||||
}
|
25
src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
Normal file
25
src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package io.kamax.mxisd.hash.storage;
|
||||
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class EmptyStorage implements HashStorage {
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
}
|
||||
}
|
15
src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
Normal file
15
src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package io.kamax.mxisd.hash.storage;
|
||||
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface HashStorage {
|
||||
|
||||
Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes);
|
||||
|
||||
void add(ThreePidMapping pidMapping, String hash);
|
||||
|
||||
void clear();
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package io.kamax.mxisd.hash.storage;
|
||||
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class InMemoryHashStorage implements HashStorage {
|
||||
|
||||
private final Map<String, ThreePidMapping> mapping = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
List<Pair<String, ThreePidMapping>> result = new ArrayList<>();
|
||||
for (String hash : hashes) {
|
||||
ThreePidMapping pidMapping = mapping.get(hash);
|
||||
if (pidMapping != null) {
|
||||
result.add(Pair.of(hash, pidMapping));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
mapping.put(hash, pidMapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
mapping.clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package io.kamax.mxisd.hash.storage;
|
||||
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class SqlHashStorage implements HashStorage {
|
||||
|
||||
private final IStorage storage;
|
||||
|
||||
public SqlHashStorage(IStorage storage) {
|
||||
this.storage = storage;
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(storage::clearHashes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
return storage.findHashes(hashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
storage.addHash(pidMapping.getMxid(), pidMapping.getMedium(), pidMapping.getValue(), hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
storage.clearHashes();
|
||||
}
|
||||
}
|
@@ -25,8 +25,6 @@ public class IsAPIv1 {
|
||||
public static final String Base = "/_matrix/identity/api/v1";
|
||||
|
||||
public static String getValidate(String medium, String sid, String secret, String token) {
|
||||
// FIXME use some kind of URLBuilder
|
||||
return Base + "/validate/" + medium + "/submitToken?sid=" + sid + "&client_secret=" + secret + "&token=" + token;
|
||||
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s", Base, medium, sid, secret, token);
|
||||
}
|
||||
|
||||
}
|
||||
|
31
src/main/java/io/kamax/mxisd/http/IsAPIv2.java
Normal file
31
src/main/java/io/kamax/mxisd/http/IsAPIv2.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http;
|
||||
|
||||
public class IsAPIv2 {
|
||||
|
||||
public static final String Base = "/_matrix/identity/v2";
|
||||
|
||||
public static String getValidate(String medium, String sid, String secret, String token) {
|
||||
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s", Base, medium, sid, secret, token);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.io.identity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClientHashLookupAnswer {
|
||||
|
||||
private Map<String, String> mappings = new HashMap<>();
|
||||
|
||||
public Map<String, String> getMappings() {
|
||||
return mappings;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.io.identity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ClientHashLookupRequest {
|
||||
|
||||
private String algorithm;
|
||||
private String pepper;
|
||||
private List<String> addresses = new ArrayList<>();
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public String getPepper() {
|
||||
return pepper;
|
||||
}
|
||||
|
||||
public void setPepper(String pepper) {
|
||||
this.pepper = pepper;
|
||||
}
|
||||
|
||||
public List<String> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(List<String> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package io.kamax.mxisd.http.undertow.conduit;
|
||||
|
||||
public interface ConduitWithDump {
|
||||
String dump();
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2014 Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.conduit;
|
||||
|
||||
import org.xnio.IoUtils;
|
||||
import org.xnio.channels.StreamSourceChannel;
|
||||
import org.xnio.conduits.AbstractStreamSinkConduit;
|
||||
import org.xnio.conduits.ConduitWritableByteChannel;
|
||||
import org.xnio.conduits.Conduits;
|
||||
import org.xnio.conduits.StreamSinkConduit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Conduit that saves all the data that is written through it and can dump it to the console
|
||||
* <p>
|
||||
* Obviously this should not be used in production.
|
||||
*
|
||||
* @author Stuart Douglas
|
||||
*/
|
||||
public class DebuggingStreamSinkConduit extends AbstractStreamSinkConduit<StreamSinkConduit> implements ConduitWithDump {
|
||||
|
||||
private final List<byte[]> data = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct a new instance.
|
||||
*
|
||||
* @param next the delegate conduit to set
|
||||
*/
|
||||
public DebuggingStreamSinkConduit(StreamSinkConduit next) {
|
||||
super(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) throws IOException {
|
||||
int pos = src.position();
|
||||
int res = super.write(src);
|
||||
if (res > 0) {
|
||||
byte[] d = new byte[res];
|
||||
for (int i = 0; i < res; ++i) {
|
||||
d[i] = src.get(i + pos);
|
||||
}
|
||||
data.add(d);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long write(ByteBuffer[] dsts, int offs, int len) throws IOException {
|
||||
for (int i = offs; i < len; ++i) {
|
||||
if (dsts[i].hasRemaining()) {
|
||||
return write(dsts[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long transferFrom(final FileChannel src, final long position, final long count) throws IOException {
|
||||
return src.transferTo(position, count, new ConduitWritableByteChannel(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException {
|
||||
return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeFinal(ByteBuffer src) throws IOException {
|
||||
return Conduits.writeFinalBasic(this, src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException {
|
||||
return Conduits.writeFinalBasic(this, srcs, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte[] datum : data) {
|
||||
sb.append(new String(datum, StandardCharsets.UTF_8));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2014 Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.conduit;
|
||||
|
||||
import org.xnio.IoUtils;
|
||||
import org.xnio.channels.StreamSinkChannel;
|
||||
import org.xnio.conduits.AbstractStreamSourceConduit;
|
||||
import org.xnio.conduits.ConduitReadableByteChannel;
|
||||
import org.xnio.conduits.StreamSourceConduit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Conduit that saves all the data that is written through it and can dump it to the console
|
||||
* <p>
|
||||
* Obviously this should not be used in production.
|
||||
*
|
||||
* @author Stuart Douglas
|
||||
*/
|
||||
public class DebuggingStreamSourceConduit extends AbstractStreamSourceConduit<StreamSourceConduit> implements ConduitWithDump {
|
||||
|
||||
private final List<byte[]> data = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct a new instance.
|
||||
*
|
||||
* @param next the delegate conduit to set
|
||||
*/
|
||||
public DebuggingStreamSourceConduit(StreamSourceConduit next) {
|
||||
super(next);
|
||||
}
|
||||
|
||||
public long transferTo(final long position, final long count, final FileChannel target) throws IOException {
|
||||
return target.transferFrom(new ConduitReadableByteChannel(this), position, count);
|
||||
}
|
||||
|
||||
public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException {
|
||||
return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
int pos = dst.position();
|
||||
int res = super.read(dst);
|
||||
if (res > 0) {
|
||||
byte[] d = new byte[res];
|
||||
for (int i = 0; i < res; ++i) {
|
||||
d[i] = dst.get(i + pos);
|
||||
}
|
||||
data.add(d);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long read(ByteBuffer[] dsts, int offs, int len) throws IOException {
|
||||
for (int i = offs; i < len; ++i) {
|
||||
if (dsts[i].hasRemaining()) {
|
||||
return read(dsts[i]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte[] datum : data) {
|
||||
sb.append(new String(datum, StandardCharsets.UTF_8));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package io.kamax.mxisd.http.undertow.conduit;
|
||||
|
||||
import io.undertow.server.ConduitWrapper;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.util.ConduitFactory;
|
||||
import org.xnio.conduits.Conduit;
|
||||
|
||||
public abstract class LazyConduitWrapper<T extends Conduit> implements ConduitWrapper<T> {
|
||||
|
||||
private T conduit = null;
|
||||
|
||||
protected abstract T create(ConduitFactory<T> factory, HttpServerExchange exchange);
|
||||
|
||||
@Override
|
||||
public T wrap(ConduitFactory<T> factory, HttpServerExchange exchange) {
|
||||
conduit = create(factory, exchange);
|
||||
return conduit;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return conduit;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.IsAPIv2;
|
||||
import io.kamax.mxisd.matrix.IdentityServiceAPI;
|
||||
import io.undertow.server.HttpHandler;
|
||||
|
||||
public interface ApiHandler extends HttpHandler {
|
||||
|
||||
default String getPath(IdentityServiceAPI api) {
|
||||
switch (api) {
|
||||
case V2:
|
||||
return IsAPIv2.Base + getHandlerPath();
|
||||
case V1:
|
||||
return IsAPIv1.Base + getHandlerPath();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown api version: " + api);
|
||||
}
|
||||
}
|
||||
|
||||
String getHandlerPath();
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AuthorizationHandler extends BasicHttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthorizationHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
private final HttpHandler child;
|
||||
|
||||
public static AuthorizationHandler around(AccountManager accountManager, HttpHandler child) {
|
||||
return new AuthorizationHandler(accountManager, child);
|
||||
}
|
||||
|
||||
private AuthorizationHandler(AccountManager accountManager, HttpHandler child) {
|
||||
this.accountManager = accountManager;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
String token = findAccessToken(exchange).orElse(null);
|
||||
if (token == null) {
|
||||
log.error("Unauthorized request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
AccountDao account = accountManager.findAccount(token);
|
||||
if (account == null) {
|
||||
log.error("Account not found from request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
long expiredAt = (account.getCreatedAt() + account.getExpiresIn()) * 1000; // expired in milliseconds
|
||||
if (expiredAt < System.currentTimeMillis()) {
|
||||
log.error("Account for '{}' from: {}", account.getUserId(), exchange.getHostAndPort());
|
||||
accountManager.deleteAccount(token);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
log.trace("Access for '{}' allowed", account.getUserId());
|
||||
child.handleRequest(exchange);
|
||||
}
|
||||
}
|
@@ -28,6 +28,7 @@ import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||
import io.kamax.mxisd.exception.HttpMatrixException;
|
||||
import io.kamax.mxisd.exception.InternalServerError;
|
||||
import io.kamax.mxisd.proxy.Response;
|
||||
import io.kamax.mxisd.util.OptionalUtil;
|
||||
import io.kamax.mxisd.util.RestClientUtils;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
@@ -55,6 +56,24 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BasicHttpHandler.class);
|
||||
|
||||
protected final static String headerName = "Authorization";
|
||||
protected final static String headerValuePrefix = "Bearer ";
|
||||
private final static String parameterName = "access_token";
|
||||
|
||||
Optional<String> findAccessTokenInHeaders(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName))
|
||||
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
|
||||
.map(header -> header.substring(headerValuePrefix.length()));
|
||||
}
|
||||
|
||||
Optional<String> findAccessTokenInQuery(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst());
|
||||
}
|
||||
|
||||
public Optional<String> findAccessToken(HttpServerExchange exchange) {
|
||||
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange));
|
||||
}
|
||||
|
||||
protected String getAccessToken(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
.flatMap(v -> {
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CheckTermsHandler extends BasicHttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CheckTermsHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
private final HttpHandler child;
|
||||
|
||||
private final List<PolicyConfig.PolicyObject> policies;
|
||||
|
||||
public static CheckTermsHandler around(AccountManager accountManager, HttpHandler child, List<PolicyConfig.PolicyObject> policies) {
|
||||
return new CheckTermsHandler(accountManager, child, policies);
|
||||
}
|
||||
|
||||
private CheckTermsHandler(AccountManager accountManager, HttpHandler child,
|
||||
List<PolicyConfig.PolicyObject> policies) {
|
||||
this.accountManager = accountManager;
|
||||
this.child = child;
|
||||
this.policies = policies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
if (policies == null || policies.isEmpty()) {
|
||||
child.handleRequest(exchange);
|
||||
return;
|
||||
}
|
||||
|
||||
String token = findAccessToken(exchange).orElse(null);
|
||||
if (token == null) {
|
||||
log.error("Unauthorized request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
if (!accountManager.isTermAccepted(token, policies)) {
|
||||
log.error("Non accepting request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
log.trace("Access granted");
|
||||
child.handleRequest(exchange);
|
||||
}
|
||||
}
|
@@ -21,35 +21,11 @@
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||
import io.kamax.mxisd.util.OptionalUtil;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class HomeserverProxyHandler extends BasicHttpHandler {
|
||||
|
||||
protected final static String headerName = "Authorization";
|
||||
protected final static String headerValuePrefix = "Bearer ";
|
||||
private final static String parameterName = "access_token";
|
||||
|
||||
Optional<String> findAccessTokenInHeaders(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName))
|
||||
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
|
||||
.map(header -> header.substring(headerValuePrefix.length()));
|
||||
}
|
||||
|
||||
Optional<String> findAccessTokenInQuery(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst());
|
||||
}
|
||||
|
||||
public Optional<String> findAccessToken(HttpServerExchange exchange) {
|
||||
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange));
|
||||
}
|
||||
|
||||
public String getAccessToken(HttpServerExchange exchange) {
|
||||
return findAccessToken(exchange).orElseThrow(AccessTokenNotFoundException::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2014 Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.http.undertow.conduit.ConduitWithDump;
|
||||
import io.kamax.mxisd.http.undertow.conduit.DebuggingStreamSinkConduit;
|
||||
import io.kamax.mxisd.http.undertow.conduit.DebuggingStreamSourceConduit;
|
||||
import io.kamax.mxisd.http.undertow.conduit.LazyConduitWrapper;
|
||||
import io.undertow.security.api.SecurityContext;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.handlers.Cookie;
|
||||
import io.undertow.util.ConduitFactory;
|
||||
import io.undertow.util.HeaderValues;
|
||||
import io.undertow.util.Headers;
|
||||
import io.undertow.util.LocaleUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xnio.conduits.StreamSinkConduit;
|
||||
import org.xnio.conduits.StreamSourceConduit;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handler that dumps a exchange to a log.
|
||||
*
|
||||
* @author Stuart Douglas
|
||||
*/
|
||||
public class RequestDumpingHandler implements HttpHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RequestDumpingHandler.class);
|
||||
|
||||
private final HttpHandler next;
|
||||
|
||||
public RequestDumpingHandler(HttpHandler next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
LazyConduitWrapper<StreamSourceConduit> requestConduitWrapper = new LazyConduitWrapper<StreamSourceConduit>() {
|
||||
@Override
|
||||
protected StreamSourceConduit create(ConduitFactory<StreamSourceConduit> factory, HttpServerExchange exchange) {
|
||||
return new DebuggingStreamSourceConduit(factory.create());
|
||||
}
|
||||
};
|
||||
LazyConduitWrapper<StreamSinkConduit> responseConduitWrapper = new LazyConduitWrapper<StreamSinkConduit>() {
|
||||
@Override
|
||||
protected StreamSinkConduit create(ConduitFactory<StreamSinkConduit> factory, HttpServerExchange exchange) {
|
||||
return new DebuggingStreamSinkConduit(factory.create());
|
||||
}
|
||||
};
|
||||
exchange.addRequestWrapper(requestConduitWrapper);
|
||||
exchange.addResponseWrapper(responseConduitWrapper);
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
// Log pre-service information
|
||||
final SecurityContext sc = exchange.getSecurityContext();
|
||||
sb.append("\n----------------------------REQUEST---------------------------\n");
|
||||
sb.append(" URI=").append(exchange.getRequestURI()).append("\n");
|
||||
sb.append(" characterEncoding=").append(exchange.getRequestHeaders().get(Headers.CONTENT_ENCODING)).append("\n");
|
||||
sb.append(" contentLength=").append(exchange.getRequestContentLength()).append("\n");
|
||||
sb.append(" contentType=").append(exchange.getRequestHeaders().get(Headers.CONTENT_TYPE)).append("\n");
|
||||
//sb.append(" contextPath=" + exchange.getContextPath());
|
||||
if (sc != null) {
|
||||
if (sc.isAuthenticated()) {
|
||||
sb.append(" authType=").append(sc.getMechanismName()).append("\n");
|
||||
sb.append(" principle=").append(sc.getAuthenticatedAccount().getPrincipal()).append("\n");
|
||||
} else {
|
||||
sb.append(" authType=none\n");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Cookie> cookies = exchange.getRequestCookies();
|
||||
if (cookies != null) {
|
||||
for (Map.Entry<String, Cookie> entry : cookies.entrySet()) {
|
||||
Cookie cookie = entry.getValue();
|
||||
sb.append(" cookie=").append(cookie.getName()).append("=").append(cookie.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
for (HeaderValues header : exchange.getRequestHeaders()) {
|
||||
for (String value : header) {
|
||||
sb.append(" header=").append(header.getHeaderName()).append("=").append(value).append("\n");
|
||||
}
|
||||
}
|
||||
sb.append(" locale=").append(LocaleUtils.getLocalesFromHeader(exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE)))
|
||||
.append("\n");
|
||||
sb.append(" method=").append(exchange.getRequestMethod()).append("\n");
|
||||
Map<String, Deque<String>> pnames = exchange.getQueryParameters();
|
||||
for (Map.Entry<String, Deque<String>> entry : pnames.entrySet()) {
|
||||
String pname = entry.getKey();
|
||||
Iterator<String> pvalues = entry.getValue().iterator();
|
||||
sb.append(" parameter=");
|
||||
sb.append(pname);
|
||||
sb.append('=');
|
||||
while (pvalues.hasNext()) {
|
||||
sb.append(pvalues.next());
|
||||
if (pvalues.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
//sb.append(" pathInfo=" + exchange.getPathInfo());
|
||||
sb.append(" protocol=").append(exchange.getProtocol()).append("\n");
|
||||
sb.append(" queryString=").append(exchange.getQueryString()).append("\n");
|
||||
sb.append(" remoteAddr=").append(exchange.getSourceAddress()).append("\n");
|
||||
sb.append(" remoteHost=").append(exchange.getSourceAddress().getHostName()).append("\n");
|
||||
//sb.append("requestedSessionId=" + exchange.getRequestedSessionId());
|
||||
sb.append(" scheme=").append(exchange.getRequestScheme()).append("\n");
|
||||
sb.append(" host=").append(exchange.getRequestHeaders().getFirst(Headers.HOST)).append("\n");
|
||||
sb.append(" serverPort=").append(exchange.getDestinationAddress().getPort()).append("\n");
|
||||
//sb.append(" servletPath=" + exchange.getServletPath());
|
||||
sb.append(" isSecure=").append(exchange.isSecure()).append("\n");
|
||||
|
||||
exchange.addExchangeCompleteListener((exchange1, nextListener) -> {
|
||||
StreamSourceConduit sourceConduit = requestConduitWrapper.get();
|
||||
if (sourceConduit instanceof ConduitWithDump) {
|
||||
ConduitWithDump conduitWithDump = (ConduitWithDump) sourceConduit;
|
||||
sb.append("body=\n");
|
||||
sb.append(conduitWithDump.dump()).append("\n");
|
||||
}
|
||||
|
||||
// Log post-service information
|
||||
sb.append("--------------------------RESPONSE--------------------------\n");
|
||||
if (sc != null) {
|
||||
if (sc.isAuthenticated()) {
|
||||
sb.append(" authType=").append(sc.getMechanismName()).append("\n");
|
||||
sb.append(" principle=").append(sc.getAuthenticatedAccount().getPrincipal()).append("\n");
|
||||
} else {
|
||||
sb.append(" authType=none\n");
|
||||
}
|
||||
}
|
||||
sb.append(" contentLength=").append(exchange1.getResponseContentLength()).append("\n");
|
||||
sb.append(" contentType=").append(exchange1.getResponseHeaders().getFirst(Headers.CONTENT_TYPE)).append("\n");
|
||||
Map<String, Cookie> cookies1 = exchange1.getResponseCookies();
|
||||
if (cookies1 != null) {
|
||||
for (Cookie cookie : cookies1.values()) {
|
||||
sb.append(" cookie=").append(cookie.getName()).append("=").append(cookie.getValue()).append("; domain=")
|
||||
.append(cookie.getDomain()).append("; path=").append(cookie.getPath()).append("\n");
|
||||
}
|
||||
}
|
||||
for (HeaderValues header : exchange1.getResponseHeaders()) {
|
||||
for (String value : header) {
|
||||
sb.append(" header=").append(header.getHeaderName()).append("=").append(value).append("\n");
|
||||
}
|
||||
}
|
||||
sb.append(" status=").append(exchange1.getStatusCode()).append("\n");
|
||||
StreamSinkConduit streamSinkConduit = responseConduitWrapper.get();
|
||||
if (streamSinkConduit instanceof ConduitWithDump) {
|
||||
ConduitWithDump conduitWithDump = (ConduitWithDump) streamSinkConduit;
|
||||
sb.append("body=\n");
|
||||
sb.append(conduitWithDump.dump());
|
||||
|
||||
}
|
||||
|
||||
sb.append("\n==============================================================");
|
||||
|
||||
|
||||
nextListener.proceed();
|
||||
LOGGER.info(sb.toString());
|
||||
});
|
||||
|
||||
|
||||
// Perform the exchange
|
||||
next.handleRequest(exchange);
|
||||
}
|
||||
}
|
@@ -87,6 +87,10 @@ public class SaneHandler extends BasicHttpHandler {
|
||||
respond(exchange, HttpStatus.SC_NOT_FOUND, "M_NOT_FOUND", e.getMessage());
|
||||
} catch (NotImplementedException e) {
|
||||
respond(exchange, HttpStatus.SC_NOT_IMPLEMENTED, "M_NOT_IMPLEMENTED", e.getMessage());
|
||||
} catch (InvalidPepperException e) {
|
||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PEPPER", e.getMessage());
|
||||
} catch (InvalidParamException e) {
|
||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PARAM", e.getMessage());
|
||||
} catch (FeatureNotAvailable e) {
|
||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||
log.error("Feature not available: {}", e.getInternalReason());
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountGetUserInfoHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountGetUserInfoHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountGetUserInfoHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
LOGGER.info("Get User Info.");
|
||||
String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new);
|
||||
|
||||
String userId = accountManager.getUserId(token);
|
||||
LOGGER.info("Account found: {}", userId);
|
||||
respond(exchange, GsonUtil.makeObj("user_id", userId));
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountLogoutHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account/logout";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountLogoutHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountLogoutHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
LOGGER.info("Logout.");
|
||||
String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new);
|
||||
|
||||
accountManager.logout(token);
|
||||
|
||||
respondJson(exchange, "{}");
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.auth.OpenIdToken;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class AccountRegisterHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account/register";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountRegisterHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountRegisterHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
OpenIdToken openIdToken = parseJsonTo(exchange, OpenIdToken.class);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(),
|
||||
new Date(System.currentTimeMillis() + openIdToken.getExpiresIn()));
|
||||
}
|
||||
|
||||
String token = accountManager.register(openIdToken);
|
||||
respond(exchange, GsonUtil.makeObj("token", token));
|
||||
}
|
||||
}
|
@@ -18,18 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/ephemeral/isvalid";
|
||||
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EphemeralKeyIsValidHandler.class);
|
||||
|
||||
@@ -48,4 +46,8 @@ public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
||||
respondJson(exchange, mgr.isValid(KeyType.Ephemeral, pubKey) ? validKey : invalidKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/ephemeral/isvalid";
|
||||
}
|
||||
}
|
@@ -18,19 +18,21 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
public class HelloHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base;
|
||||
public class HelloHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
respondJson(exchange, "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -18,23 +18,22 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.crypto.GenericKeyIdentifier;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class KeyGetHandler extends BasicHttpHandler {
|
||||
public class KeyGetHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
public static final String Key = "key";
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/{" + Key + "}";
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(KeyGetHandler.class);
|
||||
|
||||
@@ -61,4 +60,8 @@ public class KeyGetHandler extends BasicHttpHandler {
|
||||
respond(exchange, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/{" + Key + "}";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.http.io.identity.KeyValidityJson;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.lookup.ALookupRequest;
|
@@ -18,18 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/isvalid";
|
||||
public class RegularKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(RegularKeyIsValidHandler.class);
|
||||
|
||||
@@ -48,4 +46,8 @@ public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
||||
respondJson(exchange, mgr.isValid(KeyType.Regular, pubKey) ? validKey : invalidKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/isvalid";
|
||||
}
|
||||
}
|
@@ -18,26 +18,25 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
||||
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
||||
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionStartHandler extends BasicHttpHandler {
|
||||
public class SessionStartHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
public static final String Medium = "medium";
|
||||
public static final String Path = IsAPIv1.Base + "/validate/{" + Medium + "}/requestToken";
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
||||
|
||||
@@ -84,4 +83,8 @@ public class SessionStartHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/validate/{" + Medium + "}/requestToken";
|
||||
}
|
||||
}
|
@@ -18,16 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.BindRequest;
|
||||
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
@@ -42,9 +42,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
||||
public class SessionTpidBindHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
||||
|
||||
@@ -97,4 +95,8 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/bind";
|
||||
}
|
||||
}
|
@@ -18,21 +18,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
||||
public class SessionTpidGetValidatedHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
||||
|
||||
@@ -62,4 +60,8 @@ public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/getValidated3pid";
|
||||
}
|
||||
}
|
@@ -18,19 +18,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
||||
public class SessionTpidUnbindHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
||||
|
||||
@@ -48,4 +46,9 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
sessionMgr.unbind(auth, body);
|
||||
writeBodyAsUtf8(exchange, "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/unbind";
|
||||
}
|
||||
}
|
@@ -18,19 +18,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.kamax.mxisd.session.ValidationResult;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class SessionValidateHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/validate/{medium}/submitToken";
|
||||
public abstract class SessionValidateHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
||||
|
||||
@@ -52,4 +50,8 @@ public abstract class SessionValidateHandler extends BasicHttpHandler {
|
||||
return mgr.validate(sid, secret, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/validate/{medium}/submitToken";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
@@ -27,17 +27,15 @@ import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.matrix.json.MatrixJson;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SignEd25519Handler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/sign-ed25519";
|
||||
public class SignEd25519Handler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SignEd25519Handler.class);
|
||||
|
||||
@@ -72,4 +70,8 @@ public class SignEd25519Handler extends BasicHttpHandler {
|
||||
respondJson(exchange, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/sign-ed25519";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
@@ -28,10 +28,10 @@ import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.StoreInviteRequest;
|
||||
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.IThreePidInvite;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
@@ -45,9 +45,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
public class StoreInviteHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/store-invite";
|
||||
public class StoreInviteHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private ServerConfig cfg;
|
||||
private InvitationManager invMgr;
|
||||
@@ -100,4 +98,8 @@ public class StoreInviteHandler extends BasicHttpHandler {
|
||||
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getServerSigningKey().getId()), cfg.getPublicUrl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/store-invite";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user