Compare commits
47 Commits
ISSUE-26
...
ext/new_sy
Author | SHA1 | Date | |
---|---|---|---|
0f3c37bf6a | |||
|
ae5864cd91 | ||
|
e456724caf | ||
|
ed9dcc4061 | ||
|
ea8e386939 | ||
|
e0ec887118 | ||
|
a71d32ba77 | ||
|
a0f6fe9b0d | ||
|
c25647156a | ||
|
e7c4c12a98 | ||
|
90b2b5301c | ||
|
0d93a26e6d | ||
|
c29fc0f0eb | ||
|
e421c851c9 | ||
|
5b2b45233a | ||
|
888f7a4209 | ||
|
0c301a49c7 | ||
|
1fda2dd3b7 | ||
|
c4a20efe5e | ||
|
fc45f1b090 | ||
|
1480507d76 | ||
|
a1ab1e8e0a | ||
|
1e5033b461 | ||
|
7323851c6e | ||
|
08db73e55b | ||
|
9fba20475b | ||
|
9af5fce014 | ||
|
9843e14c1a | ||
|
60e6f1e23c | ||
|
6cdbcc69c7 | ||
|
ed7c714738 | ||
|
a9d783192b | ||
|
2bb5a734d1 | ||
|
9aa5c4cca9 | ||
|
9c4faab5d8 | ||
|
53c4ffdc4e | ||
|
e4144e923a | ||
|
791361c10d | ||
|
7c94bd4744 | ||
|
4b5eecd7e7 | ||
|
a6968fb7e9 | ||
|
d4853b1154 | ||
|
89df4b2425 | ||
|
0f89121b98 | ||
|
8a40ca185b | ||
|
5baeb42623 | ||
|
072e5f66cb |
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 3 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['java']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
10
Dockerfile
10
Dockerfile
@@ -1,3 +1,11 @@
|
||||
FROM --platform=$BUILDPLATFORM openjdk:8-jre-alpine AS builder
|
||||
|
||||
RUN apk update && apk add gradle git && rm -rf /var/lib/apk/* /var/cache/apk/*
|
||||
|
||||
WORKDIR /ma1sd
|
||||
COPY . .
|
||||
RUN ./gradlew shadowJar
|
||||
|
||||
FROM openjdk:8-jre-alpine
|
||||
|
||||
RUN apk update && apk add bash && rm -rf /var/lib/apk/* /var/cache/apk/*
|
||||
@@ -15,4 +23,4 @@ CMD [ "/start.sh" ]
|
||||
|
||||
ADD src/docker/start.sh /start.sh
|
||||
ADD src/script/ma1sd /app/ma1sd
|
||||
ADD build/libs/ma1sd.jar /app/ma1sd.jar
|
||||
COPY --from=builder /ma1sd/build/libs/ma1sd.jar /app/ma1sd.jar
|
||||
|
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
|
95
build.gradle
95
build.gradle
@@ -20,7 +20,7 @@
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'idea'
|
||||
@@ -73,90 +73,94 @@ String gitVersion() {
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Logging
|
||||
compile 'org.slf4j:slf4j-simple:1.7.25'
|
||||
api 'org.slf4j:slf4j-simple:1.7.25'
|
||||
|
||||
// Easy file management
|
||||
compile 'commons-io:commons-io:2.6'
|
||||
api 'commons-io:commons-io:2.8.0'
|
||||
|
||||
// Config management
|
||||
compile 'org.yaml:snakeyaml:1.25'
|
||||
api 'org.yaml:snakeyaml:1.28'
|
||||
|
||||
// Dependencies from old Matrix-java-sdk
|
||||
compile 'org.apache.commons:commons-lang3:3.9'
|
||||
compile 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
compile 'commons-codec:commons-codec:1.13'
|
||||
api 'org.apache.commons:commons-lang3:3.12.0'
|
||||
api 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
api 'commons-codec:commons-codec:1.15'
|
||||
|
||||
// ORMLite
|
||||
compile 'com.j256.ormlite:ormlite-jdbc:5.1'
|
||||
api 'com.j256.ormlite:ormlite-jdbc:5.3'
|
||||
|
||||
// ed25519 handling
|
||||
compile 'net.i2p.crypto:eddsa:0.3.0'
|
||||
api 'net.i2p.crypto:eddsa:0.3.0'
|
||||
|
||||
// LDAP connector
|
||||
compile 'org.apache.directory.api:api-all:1.0.3'
|
||||
api 'org.apache.directory.api:api-all:1.0.3'
|
||||
|
||||
// DNS lookups
|
||||
compile 'dnsjava:dnsjava:2.1.9'
|
||||
api 'dnsjava:dnsjava:2.1.9'
|
||||
|
||||
// HTTP connections
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.10'
|
||||
api 'org.apache.httpcomponents:httpclient:4.5.13'
|
||||
|
||||
// Phone numbers validation
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.22'
|
||||
api 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
|
||||
|
||||
// E-mail sending
|
||||
compile 'javax.mail:javax.mail-api:1.6.2'
|
||||
compile 'com.sun.mail:javax.mail:1.6.2'
|
||||
api 'javax.mail:javax.mail-api:1.6.2'
|
||||
api 'com.sun.mail:javax.mail:1.6.2'
|
||||
|
||||
// Google Firebase Authentication backend
|
||||
compile 'com.google.firebase:firebase-admin:5.3.0'
|
||||
api 'com.google.firebase:firebase-admin:5.3.0'
|
||||
|
||||
// Connection Pool
|
||||
compile 'com.mchange:c3p0:0.9.5.4'
|
||||
api 'com.mchange:c3p0:0.9.5.5'
|
||||
|
||||
// SQLite
|
||||
compile 'org.xerial:sqlite-jdbc:3.28.0'
|
||||
api 'org.xerial:sqlite-jdbc:3.34.0'
|
||||
|
||||
// PostgreSQL
|
||||
compile 'org.postgresql:postgresql:42.2.8'
|
||||
api 'org.postgresql:postgresql:42.2.19'
|
||||
|
||||
// MariaDB/MySQL
|
||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.5.1'
|
||||
api 'org.mariadb.jdbc:mariadb-java-client:2.7.2'
|
||||
|
||||
// UNIX sockets
|
||||
api 'com.kohlschutter.junixsocket:junixsocket-core:2.3.3'
|
||||
|
||||
// Twilio SDK for SMS
|
||||
compile 'com.twilio.sdk:twilio:7.45.0'
|
||||
api 'com.twilio.sdk:twilio:7.45.0'
|
||||
|
||||
// SendGrid SDK to send emails from GCE
|
||||
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
||||
api 'com.sendgrid:sendgrid-java:2.2.2'
|
||||
|
||||
// ZT-Exec for exec identity store
|
||||
compile 'org.zeroturnaround:zt-exec:1.11'
|
||||
api 'org.zeroturnaround:zt-exec:1.12'
|
||||
|
||||
// HTTP server
|
||||
compile 'io.undertow:undertow-core:2.0.27.Final'
|
||||
api 'io.undertow:undertow-core:2.2.7.Final'
|
||||
|
||||
// Command parser for AS interface
|
||||
implementation 'commons-cli:commons-cli:1.4'
|
||||
api 'commons-cli:commons-cli:1.4'
|
||||
|
||||
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'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'com.github.tomakehurst:wiremock:2.27.2'
|
||||
testImplementation 'com.unboundid:unboundid-ldapsdk:4.0.12'
|
||||
testImplementation 'com.icegreen:greenmail:1.5.11'
|
||||
}
|
||||
|
||||
jar {
|
||||
@@ -239,7 +243,7 @@ task debBuild(dependsOn: shadowJar) {
|
||||
|
||||
ant.chmod(
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
perm: 'a+x'
|
||||
perm: '0755'
|
||||
)
|
||||
|
||||
ant.chmod(
|
||||
@@ -264,7 +268,7 @@ task debBuild(dependsOn: shadowJar) {
|
||||
}
|
||||
}
|
||||
|
||||
task dockerBuild(type: Exec, dependsOn: shadowJar) {
|
||||
task dockerBuild(type: Exec) {
|
||||
commandLine 'docker', 'build', '-t', dockerImageTag, project.rootDir
|
||||
|
||||
doLast {
|
||||
@@ -274,6 +278,15 @@ task dockerBuild(type: Exec, dependsOn: shadowJar) {
|
||||
}
|
||||
}
|
||||
|
||||
task dockerBuildX(type: Exec, dependsOn: shadowJar) {
|
||||
commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', dockerImageTag , project.rootDir
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', "${dockerImageName}:latest-dev", project.rootDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerPush(type: Exec) {
|
||||
commandLine 'docker', 'push', dockerImageTag
|
||||
|
||||
@@ -283,3 +296,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -110,7 +110,7 @@ For sql provider (i.e. for the `synapseSql`):
|
||||
```.yaml
|
||||
synapseSql:
|
||||
lookup:
|
||||
query: 'select user_id as mxid, medium, address from user_threepids' # query for retrive 3PIDs for hashes.
|
||||
query: 'select user_id as mxid, medium, address from user_threepid_id_server' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
For general sql provider:
|
||||
|
@@ -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)
|
||||
|
@@ -84,6 +84,7 @@ See [the migration instruction](migration-to-postgresql.md) from sqlite to postg
|
||||
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`.
|
||||
@@ -100,6 +101,11 @@ Default value for app level: `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)
|
||||
|
@@ -121,15 +121,13 @@ server {
|
||||
}
|
||||
```
|
||||
|
||||
### Synapse
|
||||
### Synapse (Deprecated with synapse v1.4.0)
|
||||
Add your ma1sd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.
|
||||
In a typical configuration, you would end up with something similar to:
|
||||
```yaml
|
||||
trusted_third_party_id_servers:
|
||||
- matrix.example.org
|
||||
```
|
||||
It is **highly recommended** to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration
|
||||
so only your own Identity server is authoritative for your HS.
|
||||
|
||||
## Validate (Under reconstruction)
|
||||
**NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
|
||||
|
@@ -1,7 +1,10 @@
|
||||
# Operations Guide
|
||||
- [Operations Guide](#operations-guide)
|
||||
- [Overview](#overview)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Backuo](#backup)
|
||||
- [Backup](#backup)
|
||||
- [Run](#run)
|
||||
- [Restore](#restore)
|
||||
|
||||
## Overview
|
||||
This document gives various information for the day-to-day management and operations of ma1sd.
|
||||
|
@@ -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.
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
#Thu Dec 05 22:39:36 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -22,7 +22,7 @@
|
||||
matrix:
|
||||
domain: ''
|
||||
v1: true # deprecated
|
||||
v2: false # MSC2140 API v2. Disabled by default in order to preserve backward compatibility.
|
||||
v2: true # MSC2140 API v2. Riot require enabled V2 API.
|
||||
|
||||
|
||||
################
|
||||
@@ -51,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 #
|
||||
@@ -129,13 +158,15 @@ threepid:
|
||||
### hash lookup for synapseSql provider.
|
||||
# synapseSql:
|
||||
# lookup:
|
||||
# query: 'select user_id as mxid, medium, address from user_threepids' # query for retrive 3PIDs for hashes.
|
||||
# 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
|
||||
# activeDirectory: false
|
||||
# defaultDomain: ''
|
||||
# connection:
|
||||
# host: 'ldap.domain.tld'
|
||||
# port: 389
|
||||
@@ -170,4 +201,19 @@ threepid:
|
||||
#
|
||||
|
||||
# logging:
|
||||
# root: trace # logging level
|
||||
# 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
|
||||
|
||||
|
||||
# Config invitation manager
|
||||
#invite:
|
||||
# fullDisplayName: true # print full name of the invited user (default false)
|
||||
# resolution:
|
||||
# timer: 10
|
||||
# period: seconds # search invites every 10 seconds (by default 5 minutes)
|
||||
|
||||
|
||||
# Internal API
|
||||
#internal:
|
||||
# enabled: true # default to false
|
||||
|
@@ -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
|
||||
|
@@ -23,11 +23,13 @@ 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;
|
||||
@@ -56,6 +58,7 @@ 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.internal.InternalInviteManagerHandler;
|
||||
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;
|
||||
@@ -99,9 +102,9 @@ public class HttpMxisd {
|
||||
public void start() {
|
||||
m.start();
|
||||
|
||||
HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs()));
|
||||
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
|
||||
HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs()));
|
||||
HttpHandler asUserHandler = sane(new AsUserHandler(m.getAs()));
|
||||
HttpHandler asTxnHandler = sane(new AsTransactionHandler(m.getAs()));
|
||||
HttpHandler asNotFoundHandler = sane(new AsNotFoundHandler(m.getAs()));
|
||||
|
||||
final RoutingHandler handler = Handlers.routing()
|
||||
.add("OPTIONS", "/**", sane(new OptionsHandler()))
|
||||
@@ -145,7 +148,13 @@ public class HttpMxisd {
|
||||
termsEndpoints(handler);
|
||||
hashEndpoints(handler);
|
||||
accountEndpoints(handler);
|
||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build();
|
||||
|
||||
if (m.getConfig().getInternal().isEnabled()) {
|
||||
handler.get(InternalInviteManagerHandler.PATH, new InternalInviteManagerHandler(m.getInvite()));
|
||||
}
|
||||
|
||||
ServerConfig serverConfig = m.getConfig().getServer();
|
||||
httpSrv = Undertow.builder().addHttpListener(serverConfig.getPort(), serverConfig.getHostname()).setHandler(handler).build();
|
||||
|
||||
httpSrv.start();
|
||||
}
|
||||
@@ -265,6 +274,11 @@ public class HttpMxisd {
|
||||
}
|
||||
|
||||
private HttpHandler sane(HttpHandler httpHandler) {
|
||||
return SaneHandler.around(httpHandler);
|
||||
SaneHandler handler = SaneHandler.around(httpHandler);
|
||||
if (m.getConfig().getLogging().isRequests()) {
|
||||
return new RequestDumpingHandler(handler);
|
||||
} else {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@ 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.PostgresqlStorageConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.crypto.CryptoFactory;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
@@ -68,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;
|
||||
@@ -113,17 +112,7 @@ public class Mxisd {
|
||||
|
||||
StorageConfig.BackendEnum storageBackend = cfg.getStorage().getBackend();
|
||||
StorageConfig.Provider storageProvider = cfg.getStorage().getProvider();
|
||||
switch (storageBackend) {
|
||||
case sqlite:
|
||||
store = new OrmLiteSqlStorage(storageBackend, storageProvider.getSqlite().getDatabase());
|
||||
break;
|
||||
case postgresql:
|
||||
PostgresqlStorageConfig postgresql = storageProvider.getPostgresql();
|
||||
store = new OrmLiteSqlStorage(storageBackend, postgresql.getDatabase(), postgresql.getUsername(), postgresql.getPassword());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Storage provider hasn't been configured");
|
||||
}
|
||||
store = new OrmLiteSqlStorage(storageBackend, storageProvider);
|
||||
|
||||
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
||||
signMgr = CryptoFactory.getSignatureManager(cfg, keyMgr);
|
||||
|
@@ -54,6 +54,8 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
||||
|
||||
public static final char[] CHARACTERS_TO_ESCAPE = ",#+<>;\"=*\\\\".toCharArray();
|
||||
|
||||
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||
|
||||
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||
@@ -94,7 +96,8 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid
|
||||
return BackendAuthResult.failure();
|
||||
}
|
||||
|
||||
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
|
||||
String filteredValue = escape(userFilterValue);
|
||||
String userFilter = "(" + getUidAtt() + "=" + filteredValue + ")";
|
||||
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
|
||||
|
||||
Set<String> attributes = new HashSet<>();
|
||||
@@ -162,8 +165,21 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
private String escape(String raw) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean escape;
|
||||
for (char c : raw.toCharArray()) {
|
||||
escape = false;
|
||||
for (int i = 0; i < CHARACTERS_TO_ESCAPE.length && !escape; i++) {
|
||||
escape = CHARACTERS_TO_ESCAPE[i] == c;
|
||||
}
|
||||
sb.append(escape ? "\\" + c : c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.backend.ldap;
|
||||
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||
@@ -116,10 +117,20 @@ public abstract class LdapBackend {
|
||||
|
||||
public String buildMatrixIdFromUid(String uid) {
|
||||
String uidType = getCfg().getAttribute().getUid().getType();
|
||||
String localpart = uid.toLowerCase();
|
||||
|
||||
if (!StringUtils.equals(uid, localpart)) {
|
||||
log.info("UID {} from LDAP has been changed to lowercase to match the Synapse specifications", uid);
|
||||
}
|
||||
|
||||
if (StringUtils.equals(UID, uidType)) {
|
||||
return "@" + uid + ":" + mxCfg.getDomain();
|
||||
if(getCfg().isActiveDirectory()) {
|
||||
localpart = new UPN(uid.toLowerCase()).getMXID();
|
||||
}
|
||||
|
||||
return "@" + localpart + ":" + mxCfg.getDomain();
|
||||
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
|
||||
return uid;
|
||||
return localpart;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bind type " + uidType + " is not supported");
|
||||
}
|
||||
@@ -128,6 +139,10 @@ public abstract class LdapBackend {
|
||||
public String buildUidFromMatrixId(_MatrixID mxId) {
|
||||
String uidType = getCfg().getAttribute().getUid().getType();
|
||||
if (StringUtils.equals(UID, uidType)) {
|
||||
if(getCfg().isActiveDirectory()) {
|
||||
return new UPN(mxId).getUPN();
|
||||
}
|
||||
|
||||
return mxId.getLocalPart();
|
||||
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
|
||||
return mxId.getId();
|
||||
@@ -169,4 +184,58 @@ public abstract class LdapBackend {
|
||||
return values;
|
||||
}
|
||||
|
||||
private class UPN {
|
||||
private String login;
|
||||
private String domain;
|
||||
|
||||
public UPN(String userPrincipalName) {
|
||||
String[] uidParts = userPrincipalName.split("@");
|
||||
|
||||
if (uidParts.length != 2) {
|
||||
throw new IllegalArgumentException(String.format("Wrong userPrincipalName provided: %s", userPrincipalName));
|
||||
}
|
||||
|
||||
this.login = uidParts[0];
|
||||
this.domain = uidParts[1];
|
||||
}
|
||||
|
||||
public UPN(_MatrixID mxid) {
|
||||
String[] idParts = mxid.getLocalPart().split("/");
|
||||
|
||||
if (idParts.length != 2) {
|
||||
if(idParts.length == 1 && !StringUtils.isEmpty(getCfg().getDefaultDomain())) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Local part of mxid %s does not contains domain separator and default domain is not configured",
|
||||
mxid.getLocalPart()
|
||||
));
|
||||
}
|
||||
|
||||
this.domain = getCfg().getDefaultDomain();
|
||||
} else {
|
||||
this.domain = idParts[1];
|
||||
}
|
||||
|
||||
this.login = idParts[0];
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public String getMXID() {
|
||||
if(StringUtils.equalsIgnoreCase(getCfg().getDefaultDomain(), this.domain)) {
|
||||
return this.login;
|
||||
}
|
||||
|
||||
return new StringBuilder(this.login).append("/").append(this.domain).toString();
|
||||
}
|
||||
|
||||
public String getUPN() {
|
||||
return new StringBuilder(this.login).append("@").append(this.domain).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -0,0 +1,5 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public interface DatabaseStorageConfig {
|
||||
String getDatabase();
|
||||
}
|
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class HashingConfig {
|
||||
|
||||
@@ -13,7 +14,7 @@ public class HashingConfig {
|
||||
private boolean enabled = false;
|
||||
private int pepperLength = 20;
|
||||
private RotationPolicyEnum rotationPolicy;
|
||||
private HashStorageEnum hashStorageType;
|
||||
private HashStorageEnum hashStorageType = HashStorageEnum.in_memory;
|
||||
private String delay = "10s";
|
||||
private transient long delayInSeconds = 10;
|
||||
private int requests = 10;
|
||||
@@ -25,6 +26,7 @@ public class HashingConfig {
|
||||
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());
|
||||
|
24
src/main/java/io/kamax/mxisd/config/InternalAPIConfig.java
Normal file
24
src/main/java/io/kamax/mxisd/config/InternalAPIConfig.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InternalAPIConfig {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(InternalAPIConfig.class);
|
||||
|
||||
private boolean enabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Internal API config ---");
|
||||
log.info("Internal API enabled: {}", isEnabled());
|
||||
}
|
||||
}
|
@@ -67,6 +67,7 @@ public class InvitationConfig {
|
||||
|
||||
private boolean recursive = true;
|
||||
private long timer = 5;
|
||||
private PeriodDimension period = PeriodDimension.minutes;
|
||||
|
||||
public boolean isRecursive() {
|
||||
return recursive;
|
||||
@@ -84,6 +85,13 @@ public class InvitationConfig {
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public PeriodDimension getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
public void setPeriod(PeriodDimension period) {
|
||||
this.period = period;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SenderPolicy {
|
||||
@@ -115,6 +123,7 @@ public class InvitationConfig {
|
||||
private Expiration expiration = new Expiration();
|
||||
private Resolution resolution = new Resolution();
|
||||
private Policies policy = new Policies();
|
||||
private boolean fullDisplayName = false;
|
||||
|
||||
public Expiration getExpiration() {
|
||||
return expiration;
|
||||
@@ -140,11 +149,26 @@ public class InvitationConfig {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
public boolean isFullDisplayName() {
|
||||
return fullDisplayName;
|
||||
}
|
||||
|
||||
public void setFullDisplayName(boolean fullDisplayName) {
|
||||
this.fullDisplayName = fullDisplayName;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Invite config ---");
|
||||
log.info("Expiration: {}", GsonUtil.get().toJson(getExpiration()));
|
||||
log.info("Resolution: {}", GsonUtil.get().toJson(getResolution()));
|
||||
log.info("Policies: {}", GsonUtil.get().toJson(getPolicy()));
|
||||
log.info("Print full display name on invitation: {}", isFullDisplayName());
|
||||
}
|
||||
|
||||
public enum PeriodDimension {
|
||||
|
||||
minutes,
|
||||
|
||||
seconds
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ public class LoggingConfig {
|
||||
|
||||
private String root;
|
||||
private String app;
|
||||
private boolean requests = false;
|
||||
|
||||
public String getRoot() {
|
||||
return root;
|
||||
@@ -27,6 +28,14 @@ public class LoggingConfig {
|
||||
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())) {
|
||||
@@ -43,5 +52,9 @@ public class LoggingConfig {
|
||||
} 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -118,6 +118,7 @@ public class MxisdConfig {
|
||||
private PolicyConfig policy = new PolicyConfig();
|
||||
private HashingConfig hashing = new HashingConfig();
|
||||
private LoggingConfig logging = new LoggingConfig();
|
||||
private InternalAPIConfig internal = new InternalAPIConfig();
|
||||
|
||||
public AppServiceConfig getAppsvc() {
|
||||
return appsvc;
|
||||
@@ -358,6 +359,14 @@ public class MxisdConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public InternalAPIConfig getInternal() {
|
||||
return internal;
|
||||
}
|
||||
|
||||
public void setInternal(InternalAPIConfig internal) {
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
public MxisdConfig build() {
|
||||
getLogging().build();
|
||||
|
||||
@@ -394,6 +403,7 @@ public class MxisdConfig {
|
||||
getWordpress().build();
|
||||
getPolicy().build();
|
||||
getHashing().build(getMatrix());
|
||||
getInternal().build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public class PostgresqlStorageConfig {
|
||||
public class PostgresqlStorageConfig implements DatabaseStorageConfig {
|
||||
|
||||
private String database;
|
||||
|
||||
@@ -28,6 +28,17 @@ public class PostgresqlStorageConfig {
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -51,4 +62,44 @@ public class PostgresqlStorageConfig {
|
||||
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,8 +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());
|
||||
}
|
||||
}
|
||||
|
@@ -291,6 +291,9 @@ public abstract class LdapConfig {
|
||||
private boolean enabled;
|
||||
private String filter;
|
||||
|
||||
private boolean activeDirectory;
|
||||
private String defaultDomain;
|
||||
|
||||
private Connection connection = new Connection();
|
||||
private Attribute attribute = new Attribute();
|
||||
private Auth auth = new Auth();
|
||||
@@ -316,6 +319,22 @@ public abstract class LdapConfig {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public boolean isActiveDirectory() {
|
||||
return activeDirectory;
|
||||
}
|
||||
|
||||
public void setActiveDirectory(boolean activeDirectory) {
|
||||
this.activeDirectory = activeDirectory;
|
||||
}
|
||||
|
||||
public String getDefaultDomain() {
|
||||
return defaultDomain;
|
||||
}
|
||||
|
||||
public void setDefaultDomain(String defaultDomain) {
|
||||
this.defaultDomain = defaultDomain;
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
@@ -407,6 +426,15 @@ public abstract class LdapConfig {
|
||||
throw new ConfigurationException("ldap.identity.token");
|
||||
}
|
||||
|
||||
if(isActiveDirectory()) {
|
||||
if(!StringUtils.equals(LdapBackend.UID, uidType)) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute UID type should be set to %s in Active Directory mode",
|
||||
LdapBackend.UID
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Build queries
|
||||
attribute.getThreepid().forEach((k, v) -> {
|
||||
if (StringUtils.isBlank(identity.getMedium().get(k))) {
|
||||
|
@@ -125,7 +125,7 @@ public abstract class SqlConfig {
|
||||
}
|
||||
|
||||
public static class Lookup {
|
||||
private String query = "SELECT user_id AS mxid, medium, address from user_threepids";
|
||||
private String query = "SELECT user_id AS mxid, medium, address from user_threepid_id_server";
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
@@ -140,7 +140,7 @@ public abstract class SqlConfig {
|
||||
|
||||
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() {
|
||||
|
@@ -56,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()) {
|
||||
|
@@ -23,5 +23,6 @@ package io.kamax.mxisd.exception;
|
||||
public class InvalidParamException extends RuntimeException {
|
||||
|
||||
public InvalidParamException() {
|
||||
super("The chosen hash algorithm is invalid or disallowed");
|
||||
}
|
||||
}
|
||||
|
@@ -23,5 +23,6 @@ package io.kamax.mxisd.exception;
|
||||
public class InvalidPepperException extends RuntimeException {
|
||||
|
||||
public InvalidPepperException() {
|
||||
super("The provided pepper is invalid or expired");
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* ma1sd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2020 Anatoliy SAblin
|
||||
*
|
||||
* https://www.github.com/ma1uta/ma1sd/
|
||||
*
|
||||
* 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 TermsNotSignedException extends RuntimeException {
|
||||
|
||||
public TermsNotSignedException() {
|
||||
super("Please accept our updated terms of service before continuing");
|
||||
}
|
||||
}
|
@@ -1,13 +1,11 @@
|
||||
package io.kamax.mxisd.hash.engine;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class NoneEngine implements Engine {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NoneEngine.class);
|
||||
private static final String PEPPER = RandomStringUtils.random(8, true, true);
|
||||
|
||||
@Override
|
||||
public void updateHashes() {
|
||||
@@ -16,6 +14,6 @@ public class NoneEngine implements Engine {
|
||||
|
||||
@Override
|
||||
public String getPepper() {
|
||||
return PEPPER;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@@ -20,11 +20,19 @@
|
||||
|
||||
package io.kamax.mxisd.http;
|
||||
|
||||
import static io.kamax.mxisd.util.RestClientUtils.urlEncode;
|
||||
|
||||
public class IsAPIv1 {
|
||||
|
||||
public static final String Base = "/_matrix/identity/api/v1";
|
||||
|
||||
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);
|
||||
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s",
|
||||
Base,
|
||||
medium,
|
||||
urlEncode(sid),
|
||||
urlEncode(secret),
|
||||
urlEncode(token)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -33,6 +33,7 @@ import io.kamax.mxisd.util.RestClientUtils;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.handlers.form.FormData;
|
||||
import io.undertow.util.Headers;
|
||||
import io.undertow.util.HttpString;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -189,7 +190,7 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
||||
}
|
||||
|
||||
protected void respond(HttpServerExchange ex, int status, String errCode, String error) {
|
||||
respond(ex, status, buildErrorBody(ex, errCode, error));
|
||||
respond(ex, status, buildErrorBody(ex, errCode, error != null ? error : "An error has occurred"));
|
||||
}
|
||||
|
||||
protected void handleException(HttpServerExchange exchange, HttpMatrixException ex) {
|
||||
@@ -203,26 +204,34 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
||||
}
|
||||
|
||||
protected void proxyPost(HttpServerExchange exchange, JsonObject body, CloseableHttpClient client, ClientDnsOverwrite dns) {
|
||||
proxyPost(exchange, body, client, dns, false);
|
||||
}
|
||||
|
||||
protected void proxyPost(HttpServerExchange exchange, JsonObject body, CloseableHttpClient client, ClientDnsOverwrite dns,
|
||||
boolean defaultJsonResponse) {
|
||||
String target = dns.transform(URI.create(exchange.getRequestURL())).toString();
|
||||
log.info("Requesting remote: {}", target);
|
||||
HttpPost req = RestClientUtils.post(target, GsonUtil.get(), body);
|
||||
|
||||
exchange.getRequestHeaders().forEach(header -> {
|
||||
header.forEach(v -> {
|
||||
exchange.getRequestHeaders().forEach(header -> header.forEach(v -> {
|
||||
String name = header.getHeaderName().toString();
|
||||
if (!StringUtils.startsWithIgnoreCase(name, "content-")) {
|
||||
req.addHeader(name, v);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
boolean missingJsonResponse = true;
|
||||
try (CloseableHttpResponse res = client.execute(req)) {
|
||||
exchange.setStatusCode(res.getStatusLine().getStatusCode());
|
||||
for (Header h : res.getAllHeaders()) {
|
||||
for (HeaderElement el : h.getElements()) {
|
||||
missingJsonResponse = !Headers.CONTENT_TYPE_STRING.equalsIgnoreCase(h.getName());
|
||||
exchange.getResponseHeaders().add(HttpString.tryFromString(h.getName()), el.getValue());
|
||||
}
|
||||
}
|
||||
if (defaultJsonResponse && missingJsonResponse) {
|
||||
exchange.getRequestHeaders().add(Headers.CONTENT_TYPE, "application/json");
|
||||
}
|
||||
res.getEntity().writeTo(exchange.getOutputStream());
|
||||
exchange.endExchange();
|
||||
} catch (IOException e) {
|
||||
|
@@ -23,6 +23,7 @@ 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.kamax.mxisd.exception.TermsNotSignedException;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
@@ -66,7 +67,7 @@ public class CheckTermsHandler extends BasicHttpHandler {
|
||||
|
||||
if (!accountManager.isTermAccepted(token, policies)) {
|
||||
log.error("Non accepting request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
throw new TermsNotSignedException();
|
||||
}
|
||||
log.trace("Access granted");
|
||||
child.handleRequest(exchange);
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -82,7 +82,10 @@ public class SaneHandler extends BasicHttpHandler {
|
||||
} catch (InvalidJsonException e) {
|
||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, e.getErrorCode(), e.getError());
|
||||
} catch (InvalidCredentialsException e) {
|
||||
log.error("Unauthorized: ", e);
|
||||
respond(exchange, HttpStatus.SC_UNAUTHORIZED, "M_UNAUTHORIZED", e.getMessage());
|
||||
} catch (TermsNotSignedException e) {
|
||||
respond(exchange, HttpStatus.SC_FORBIDDEN, "M_TERMS_NOT_SIGNED", e.getMessage());
|
||||
} catch (ObjectNotFoundException e) {
|
||||
respond(exchange, HttpStatus.SC_NOT_FOUND, "M_NOT_FOUND", e.getMessage());
|
||||
} catch (NotImplementedException e) {
|
||||
|
@@ -29,10 +29,7 @@ import java.util.Optional;
|
||||
public abstract class ApplicationServiceHandler extends BasicHttpHandler {
|
||||
|
||||
protected String getToken(HttpServerExchange ex) {
|
||||
return Optional.ofNullable(ex.getQueryParameters()
|
||||
.getOrDefault("access_token", new LinkedList<>())
|
||||
.peekFirst()
|
||||
).orElse("");
|
||||
return getAccessToken(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,30 @@
|
||||
package io.kamax.mxisd.http.undertow.handler.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class InternalInviteManagerHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String PATH = "/_ma1sd/internal/admin/inv_manager";
|
||||
|
||||
private final InvitationManager invitationManager;
|
||||
private final ExecutorService executors = Executors.newFixedThreadPool(1);
|
||||
|
||||
public InternalInviteManagerHandler(InvitationManager invitationManager) {
|
||||
this.invitationManager = invitationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
executors.submit(invitationManager::doMaintenance);
|
||||
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("result", "ok");
|
||||
respond(exchange, obj);
|
||||
}
|
||||
}
|
@@ -71,7 +71,7 @@ public class Register3pidRequestTokenHandler extends BasicHttpHandler {
|
||||
throw new NotAllowedException("Your " + medium + " address cannot be used for registration");
|
||||
}
|
||||
|
||||
proxyPost(exchange, body, client, dns);
|
||||
proxyPost(exchange, body, client, dns, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -155,7 +155,17 @@ public class InvitationManager {
|
||||
log.error("Error when running background maintenance", t);
|
||||
}
|
||||
}
|
||||
}, 5000L, TimeUnit.MILLISECONDS.convert(cfg.getResolution().getTimer(), TimeUnit.MINUTES));
|
||||
}, 5000L, TimeUnit.MILLISECONDS.convert(cfg.getResolution().getTimer(), getTimeUnit()));
|
||||
}
|
||||
|
||||
private TimeUnit getTimeUnit() {
|
||||
switch (cfg.getResolution().getPeriod()) {
|
||||
case seconds:
|
||||
return TimeUnit.SECONDS;
|
||||
case minutes:
|
||||
default:
|
||||
return TimeUnit.MINUTES;
|
||||
}
|
||||
}
|
||||
|
||||
private InvitationConfig requireValid(MxisdConfig cfg) {
|
||||
@@ -176,7 +186,8 @@ public class InvitationManager {
|
||||
if (StringUtils.isBlank(cfg.getInvite().getExpiration().getResolveTo())) {
|
||||
String localpart = cfg.getAppsvc().getUser().getInviteExpired();
|
||||
if (StringUtils.isBlank(localpart)) {
|
||||
throw new ConfigurationException("Could not compute the Invitation expiration resolution target from App service user: not set");
|
||||
throw new ConfigurationException(
|
||||
"Could not compute the Invitation expiration resolution target from App service user: not set");
|
||||
}
|
||||
|
||||
cfg.getInvite().getExpiration().setResolveTo(MatrixID.asAcceptable(localpart, cfg.getMatrix().getDomain()).getId());
|
||||
@@ -198,7 +209,8 @@ public class InvitationManager {
|
||||
}
|
||||
|
||||
private String getIdForLog(IThreePidInviteReply reply) {
|
||||
return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress();
|
||||
return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite()
|
||||
.getMedium() + ":" + reply.getInvite().getAddress();
|
||||
}
|
||||
|
||||
private Optional<SingleLookupReply> lookup3pid(String medium, String address) {
|
||||
@@ -252,13 +264,16 @@ public class InvitationManager {
|
||||
}
|
||||
|
||||
String invId = computeId(invitation);
|
||||
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId());
|
||||
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(),
|
||||
invitation.getRoomId());
|
||||
IThreePidInviteReply reply = invitations.get(invId);
|
||||
if (reply != null) {
|
||||
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
|
||||
if (!StringUtils.equals(invitation.getRoomId(), reply.getInvite().getRoomId())) {
|
||||
log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(), reply.getInvite().getRoomId());
|
||||
notifMgr.sendForReply(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName(), reply.getPublicKeys()));
|
||||
log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(),
|
||||
reply.getInvite().getRoomId());
|
||||
notifMgr.sendForReply(
|
||||
new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName(), reply.getPublicKeys()));
|
||||
} else {
|
||||
// FIXME we should check attempt and send if bigger
|
||||
}
|
||||
@@ -272,7 +287,7 @@ public class InvitationManager {
|
||||
}
|
||||
|
||||
String token = RandomStringUtils.randomAlphanumeric(64);
|
||||
String displayName = invitation.getAddress().substring(0, 3) + "...";
|
||||
String displayName = getInvitedDisplayName(invitation.getAddress());
|
||||
KeyIdentifier pKeyId = keyMgr.getServerSigningKey().getId();
|
||||
KeyIdentifier eKeyId = keyMgr.generateKey(KeyType.Ephemeral);
|
||||
|
||||
@@ -295,11 +310,20 @@ public class InvitationManager {
|
||||
log.info("Storing invite under ID {}", invId);
|
||||
storage.insertInvite(reply);
|
||||
invitations.put(invId, reply);
|
||||
log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender().getDomain());
|
||||
log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(),
|
||||
invitation.getSender().getDomain());
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private String getInvitedDisplayName(String origin) {
|
||||
if (cfg.isFullDisplayName()) {
|
||||
return origin;
|
||||
} else {
|
||||
return origin.substring(0, 3) + "...";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasInvite(ThreePid tpid) {
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
if (!StringUtils.equals(tpid.getMedium(), reply.getInvite().getMedium())) {
|
||||
@@ -385,8 +409,10 @@ public class InvitationManager {
|
||||
public void publishMappingIfInvited(ThreePidMapping threePid) {
|
||||
log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue());
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
if (StringUtils.equalsIgnoreCase(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equalsIgnoreCase(reply.getInvite().getAddress(), threePid.getValue())) {
|
||||
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain());
|
||||
if (StringUtils.equalsIgnoreCase(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils
|
||||
.equalsIgnoreCase(reply.getInvite().getAddress(), threePid.getValue())) {
|
||||
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(),
|
||||
reply.getInvite().getSender().getDomain());
|
||||
publishMapping(reply, threePid.getMxid());
|
||||
}
|
||||
}
|
||||
|
@@ -130,7 +130,9 @@ public class HomeserverFederationResolver {
|
||||
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error while trying to lookup well-known for " + domain, e);
|
||||
log.info("Error while trying to lookup well-known for " + domain);
|
||||
log.trace("Error while trying to lookup well-known for " + domain, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,11 +24,16 @@ import com.j256.ormlite.dao.CloseableWrappedIterable;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
|
||||
import com.j256.ormlite.jdbc.db.PostgresDatabaseType;
|
||||
import com.j256.ormlite.jdbc.db.SqliteDatabaseType;
|
||||
import com.j256.ormlite.stmt.QueryBuilder;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.config.PostgresqlStorageConfig;
|
||||
import io.kamax.mxisd.config.SQLiteStorageConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.exception.InternalServerError;
|
||||
@@ -81,6 +86,9 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
public static class Migrations {
|
||||
public static final String FIX_ACCEPTED_DAO = "2019_12_09__2254__fix_accepted_dao";
|
||||
public static final String FIX_HASH_DAO_UNIQUE_INDEX = "2020_03_22__1153__fix_hash_dao_unique_index";
|
||||
public static final String CHANGE_TYPE_TO_TEXT_INVITE = "2020_04_21__2338__change_type_table_invites";
|
||||
public static final String CHANGE_TYPE_TO_TEXT_INVITE_HISTORY = "2020_10_26__2200__change_type_table_invite_history";
|
||||
}
|
||||
|
||||
private Dao<ThreePidInviteIO, String> invDao;
|
||||
@@ -93,22 +101,25 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
private Dao<ChangelogDao, String> changelogDao;
|
||||
private StorageConfig.BackendEnum backend;
|
||||
|
||||
public OrmLiteSqlStorage(StorageConfig.BackendEnum backend, String path) {
|
||||
this(backend, path, null, null);
|
||||
}
|
||||
|
||||
public OrmLiteSqlStorage(StorageConfig.BackendEnum backend, String database, String username, String password) {
|
||||
public OrmLiteSqlStorage(StorageConfig.BackendEnum backend, StorageConfig.Provider provider) {
|
||||
if (backend == null) {
|
||||
throw new ConfigurationException("storage.backend");
|
||||
}
|
||||
this.backend = backend;
|
||||
|
||||
if (StringUtils.isBlank(database)) {
|
||||
throw new ConfigurationException("Storage destination cannot be empty");
|
||||
withCatcher(() -> {
|
||||
ConnectionSource connPool;
|
||||
switch (backend) {
|
||||
case postgresql:
|
||||
connPool = createPostgresqlConnection(provider.getPostgresql());
|
||||
break;
|
||||
case sqlite:
|
||||
connPool = createSqliteConnection(provider.getSqlite());
|
||||
break;
|
||||
default:
|
||||
throw new ConfigurationException("storage.backend");
|
||||
}
|
||||
|
||||
withCatcher(() -> {
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + database, username, password);
|
||||
changelogDao = createDaoAndTable(connPool, ChangelogDao.class);
|
||||
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||
@@ -116,17 +127,62 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class);
|
||||
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||
acceptedDao = createDaoAndTable(connPool, AcceptedDao.class, true);
|
||||
hashDao = createDaoAndTable(connPool, HashDao.class);
|
||||
hashDao = createDaoAndTable(connPool, HashDao.class, true);
|
||||
runMigration(connPool);
|
||||
});
|
||||
}
|
||||
|
||||
private ConnectionSource createSqliteConnection(SQLiteStorageConfig config) throws SQLException {
|
||||
if (StringUtils.isBlank(config.getDatabase())) {
|
||||
throw new ConfigurationException("Storage destination cannot be empty");
|
||||
}
|
||||
|
||||
return new JdbcConnectionSource("jdbc:" + backend + ":" + config.getDatabase(), null, null, new SqliteDatabaseType());
|
||||
}
|
||||
|
||||
private ConnectionSource createPostgresqlConnection(PostgresqlStorageConfig config) throws SQLException {
|
||||
if (StringUtils.isBlank(config.getDatabase())) {
|
||||
throw new ConfigurationException("Storage destination cannot be empty");
|
||||
}
|
||||
|
||||
if (config.isPool()) {
|
||||
LOGGER.info("Enable pooling");
|
||||
JdbcPooledConnectionSource source = new JdbcPooledConnectionSource(
|
||||
"jdbc:" + backend + ":" + config.getDatabase(), config.getUsername(), config.getPassword(),
|
||||
new PostgresDatabaseType());
|
||||
source.setMaxConnectionsFree(config.getMaxConnectionsFree());
|
||||
source.setMaxConnectionAgeMillis(config.getMaxConnectionAgeMillis());
|
||||
source.setCheckConnectionsEveryMillis(config.getCheckConnectionsEveryMillis());
|
||||
source.setTestBeforeGet(config.isTestBeforeGetFromPool());
|
||||
return source;
|
||||
} else {
|
||||
return new JdbcConnectionSource("jdbc:" + backend + ":" + config.getDatabase(), config.getUsername(), config.getPassword(),
|
||||
new PostgresDatabaseType());
|
||||
}
|
||||
}
|
||||
|
||||
private void runMigration(ConnectionSource connPol) throws SQLException {
|
||||
ChangelogDao fixAcceptedDao = changelogDao.queryForId(Migrations.FIX_ACCEPTED_DAO);
|
||||
if (fixAcceptedDao == null) {
|
||||
fixAcceptedDao(connPol);
|
||||
changelogDao.create(new ChangelogDao(Migrations.FIX_ACCEPTED_DAO, new Date(), "Recreate the accepted table."));
|
||||
}
|
||||
ChangelogDao fixHashDaoUniqueIndex = changelogDao.queryForId(Migrations.FIX_HASH_DAO_UNIQUE_INDEX);
|
||||
if (fixHashDaoUniqueIndex == null) {
|
||||
fixHashDaoUniqueIndex(connPol);
|
||||
changelogDao
|
||||
.create(new ChangelogDao(Migrations.FIX_HASH_DAO_UNIQUE_INDEX, new Date(), "Add the id and migrate the unique index."));
|
||||
}
|
||||
ChangelogDao fixInviteTableColumnType = changelogDao.queryForId(Migrations.CHANGE_TYPE_TO_TEXT_INVITE);
|
||||
if (fixInviteTableColumnType == null) {
|
||||
fixInviteTableColumnType(connPol);
|
||||
changelogDao.create(new ChangelogDao(Migrations.CHANGE_TYPE_TO_TEXT_INVITE, new Date(), "Modify column type to text."));
|
||||
}
|
||||
ChangelogDao fixInviteHistoryTableColumnType = changelogDao.queryForId(Migrations.CHANGE_TYPE_TO_TEXT_INVITE_HISTORY);
|
||||
if (fixInviteHistoryTableColumnType == null) {
|
||||
fixInviteHistoryTableColumnType(connPol);
|
||||
changelogDao.create(new ChangelogDao(Migrations.CHANGE_TYPE_TO_TEXT_INVITE_HISTORY, new Date(), "Modify column type to text."));
|
||||
}
|
||||
}
|
||||
|
||||
private void fixAcceptedDao(ConnectionSource connPool) throws SQLException {
|
||||
@@ -135,6 +191,39 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
TableUtils.createTableIfNotExists(connPool, AcceptedDao.class);
|
||||
}
|
||||
|
||||
private void fixHashDaoUniqueIndex(ConnectionSource connPool) throws SQLException {
|
||||
LOGGER.info("Migration: {}", Migrations.FIX_HASH_DAO_UNIQUE_INDEX);
|
||||
TableUtils.dropTable(hashDao, true);
|
||||
TableUtils.createTableIfNotExists(connPool, HashDao.class);
|
||||
}
|
||||
|
||||
private void fixInviteTableColumnType(ConnectionSource connPool) throws SQLException {
|
||||
LOGGER.info("Migration: {}", Migrations.CHANGE_TYPE_TO_TEXT_INVITE);
|
||||
if (StorageConfig.BackendEnum.postgresql == backend) {
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column \"roomId\" type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column id type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column token type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column sender type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column medium type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column address type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid alter column properties type text");
|
||||
}
|
||||
}
|
||||
|
||||
private void fixInviteHistoryTableColumnType(ConnectionSource connPool) throws SQLException {
|
||||
LOGGER.info("Migration: {}", Migrations.CHANGE_TYPE_TO_TEXT_INVITE_HISTORY);
|
||||
if (StorageConfig.BackendEnum.postgresql == backend) {
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column \"resolvedTo\" type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column id type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column token type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column sender type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column medium type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column address type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column \"roomId\" type text");
|
||||
invDao.executeRawNoArgs("alter table invite_3pid_history alter column properties type text");
|
||||
}
|
||||
}
|
||||
|
||||
private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException {
|
||||
return createDaoAndTable(connPool, c, false);
|
||||
}
|
||||
|
@@ -6,13 +6,16 @@ import com.j256.ormlite.table.DatabaseTable;
|
||||
@DatabaseTable(tableName = "hashes")
|
||||
public class HashDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
@DatabaseField(generatedId = true)
|
||||
private Long id;
|
||||
|
||||
@DatabaseField(canBeNull = false, uniqueCombo = true)
|
||||
private String mxid;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
@DatabaseField(canBeNull = false, uniqueCombo = true)
|
||||
private String medium;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
@DatabaseField(canBeNull = false, uniqueCombo = true)
|
||||
private String address;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
@@ -28,6 +31,14 @@ public class HashDao {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getMxid() {
|
||||
return mxid;
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.test.storage;
|
||||
|
||||
import io.kamax.mxisd.config.SQLiteStorageConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
|
||||
import org.junit.Test;
|
||||
@@ -30,14 +31,22 @@ public class OrmLiteSqlStorageTest {
|
||||
|
||||
@Test
|
||||
public void insertAsTxnDuplicate() {
|
||||
OrmLiteSqlStorage store = new OrmLiteSqlStorage(StorageConfig.BackendEnum.sqlite, ":memory:");
|
||||
StorageConfig.Provider provider = new StorageConfig.Provider();
|
||||
SQLiteStorageConfig config = new SQLiteStorageConfig();
|
||||
config.setDatabase(":memory:");
|
||||
provider.setSqlite(config);
|
||||
OrmLiteSqlStorage store = new OrmLiteSqlStorage(StorageConfig.BackendEnum.sqlite, provider);
|
||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||
store.insertTransactionResult("mxisd", "2", Instant.now(), "{}");
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void insertAsTxnSame() {
|
||||
OrmLiteSqlStorage store = new OrmLiteSqlStorage(StorageConfig.BackendEnum.sqlite, ":memory:");
|
||||
StorageConfig.Provider provider = new StorageConfig.Provider();
|
||||
SQLiteStorageConfig config = new SQLiteStorageConfig();
|
||||
config.setDatabase(":memory:");
|
||||
provider.setSqlite(config);
|
||||
OrmLiteSqlStorage store = new OrmLiteSqlStorage(StorageConfig.BackendEnum.sqlite, provider);
|
||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||
}
|
||||
|
Reference in New Issue
Block a user