Compare commits
	
		
			34 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c45f95ce3f | |||
| 640fa8e9f1 | |||
| 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 | ||
|  | 7c94bd4744 | 
							
								
								
									
										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 | ||||
|   | ||||
							
								
								
									
										90
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								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 { | ||||
| @@ -275,22 +279,10 @@ 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 | ||||
|     commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', dockerImageTag , 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" | ||||
|             commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', "${dockerImageName}:latest-dev", project.rootDir | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| - [Overview](#overview) | ||||
| - [Maintenance](#maintenance) | ||||
| - [Backuo](#backup) | ||||
| - [Operations Guide](#operations-guide) | ||||
|   - [Overview](#overview) | ||||
|   - [Maintenance](#maintenance) | ||||
|   - [Backup](#backup) | ||||
|     - [Run](#run) | ||||
|     - [Restore](#restore) | ||||
|  | ||||
| ## Overview | ||||
| This document gives various information for the day-to-day management and operations of ma1sd. | ||||
|   | ||||
							
								
								
									
										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 | ||||
|   | ||||
| @@ -165,6 +165,8 @@ threepid: | ||||
| # ldap: | ||||
| #   enabled: true | ||||
| #   lookup: true # hash lookup | ||||
| #   activeDirectory: false | ||||
| #   defaultDomain: '' | ||||
| #   connection: | ||||
| #     host: 'ldap.domain.tld' | ||||
| #     port: 389 | ||||
| @@ -202,3 +204,16 @@ threepid: | ||||
| #   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 | ||||
|   | ||||
| @@ -53,7 +53,7 @@ public class MatrixPath { | ||||
|     } | ||||
|  | ||||
|     public static MatrixPath clientR0() { | ||||
|         return client().add("r0"); | ||||
|         return client().add("v3"); | ||||
|     } | ||||
|  | ||||
|     private StringBuilder path = new StringBuilder(); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -375,13 +375,13 @@ public abstract class AMatrixHttpClient implements _MatrixClientRaw { | ||||
|     } | ||||
|  | ||||
|     protected HttpUrl.Builder getClientPathBuilder(String... segments) { | ||||
|         String[] base = { "client", "r0" }; | ||||
|         String[] base = { "client", "v3" }; | ||||
|         segments = ArrayUtils.addAll(base, segments); | ||||
|         return getPathBuilder(segments); | ||||
|     } | ||||
|  | ||||
|     protected HttpUrl.Builder getMediaPathBuilder(String... segments) { | ||||
|         String[] base = { "media", "r0" }; | ||||
|         String[] base = { "media", "v3" }; | ||||
|         segments = ArrayUtils.addAll(base, segments); | ||||
|         return getPathBuilder(segments); | ||||
|     } | ||||
|   | ||||
| @@ -58,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; | ||||
| @@ -147,6 +148,11 @@ public class HttpMxisd { | ||||
|         termsEndpoints(handler); | ||||
|         hashEndpoints(handler); | ||||
|         accountEndpoints(handler); | ||||
|  | ||||
|         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(); | ||||
|  | ||||
|   | ||||
| @@ -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<>(); | ||||
| @@ -167,4 +170,16 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|   | ||||
| @@ -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))) { | ||||
|   | ||||
| @@ -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"); | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,6 @@ import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; | ||||
|  | ||||
| public abstract class LoginHandler extends BasicHttpHandler { | ||||
|  | ||||
|     public static final String Path = "/_matrix/client/r0/login"; | ||||
|     public static final String Path = "/_matrix/client/v3/login"; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ import java.net.URI; | ||||
|  | ||||
| public class UserDirectorySearchHandler extends HomeserverProxyHandler { | ||||
|  | ||||
|     public static final String Path = "/_matrix/client/r0/user_directory/search"; | ||||
|     public static final String Path = "/_matrix/client/v3/user_directory/search"; | ||||
|  | ||||
|     private DirectoryManager mgr; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -44,7 +44,7 @@ import java.util.Optional; | ||||
|  | ||||
| public class RoomInviteHandler extends BasicHttpHandler { | ||||
|  | ||||
|     public static final String Path = "/_matrix/client/r0/rooms/{roomId}/invite"; | ||||
|     public static final String Path = "/_matrix/client/v3/rooms/{roomId}/invite"; | ||||
|  | ||||
|     private static final Logger log = LoggerFactory.getLogger(RoomInviteHandler.class); | ||||
|  | ||||
| @@ -62,7 +62,7 @@ public class RoomInviteHandler extends BasicHttpHandler { | ||||
|     public void handleRequest(HttpServerExchange exchange) { | ||||
|         String accessToken = getAccessToken(exchange); | ||||
|  | ||||
|         String whoamiUri = dns.transform(URI.create(exchange.getRequestURL()).resolve(URI.create("/_matrix/client/r0/account/whoami"))).toString(); | ||||
|         String whoamiUri = dns.transform(URI.create(exchange.getRequestURL()).resolve(URI.create("/_matrix/client/v3/account/whoami"))).toString(); | ||||
|         log.info("Who Am I URL: {}", whoamiUri); | ||||
|         HttpGet whoAmIReq = new HttpGet(whoamiUri); | ||||
|         whoAmIReq.addHeader("Authorization", "Bearer " + accessToken); | ||||
|   | ||||
| @@ -34,7 +34,7 @@ import java.util.Optional; | ||||
| public class ProfileHandler extends HomeserverProxyHandler { | ||||
|  | ||||
|     public static final String UserID = "userId"; | ||||
|     public static final String Path = "/_matrix/client/r0/profile/{" + UserID + "}"; | ||||
|     public static final String Path = "/_matrix/client/v3/profile/{" + UserID + "}"; | ||||
|  | ||||
|     protected ProfileManager mgr; | ||||
|  | ||||
|   | ||||
| @@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory; | ||||
| public class Register3pidRequestTokenHandler extends BasicHttpHandler { | ||||
|  | ||||
|     public static final String Key = "medium"; | ||||
|     public static final String Path = "/_matrix/client/r0/register/{" + Key + "}/requestToken"; | ||||
|     public static final String Path = "/_matrix/client/v3/register/{" + Key + "}/requestToken"; | ||||
|  | ||||
|     private static final Logger log = LoggerFactory.getLogger(Register3pidRequestTokenHandler.class); | ||||
|  | ||||
| @@ -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(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -23,10 +23,10 @@ package io.kamax.mxisd.storage.ormlite; | ||||
| import com.j256.ormlite.dao.CloseableWrappedIterable; | ||||
| import com.j256.ormlite.dao.Dao; | ||||
| import com.j256.ormlite.dao.DaoManager; | ||||
| import com.j256.ormlite.db.PostgresDatabaseType; | ||||
| import com.j256.ormlite.db.SqliteDatabaseType; | ||||
| 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; | ||||
| @@ -88,6 +88,7 @@ public class OrmLiteSqlStorage implements IStorage { | ||||
|         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; | ||||
| @@ -177,6 +178,11 @@ public class OrmLiteSqlStorage implements IStorage { | ||||
|             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 { | ||||
| @@ -204,6 +210,20 @@ public class OrmLiteSqlStorage implements IStorage { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user