Compare commits
	
		
			33 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9c4faab5d8 | ||
|  | 53c4ffdc4e | ||
|  | e4144e923a | ||
|  | 791361c10d | ||
|  | 4b5eecd7e7 | ||
|  | a6968fb7e9 | ||
|  | d4853b1154 | ||
|  | 89df4b2425 | ||
|  | 0f89121b98 | ||
|  | 8a40ca185b | ||
|  | 5baeb42623 | ||
|  | 072e5f66cb | ||
|  | b2f41d689b | ||
|  | 9b4aff58c7 | ||
|  | a20e41574d | ||
|  | 72977d65ae | ||
|  | 7555fff1a5 | ||
|  | aed12e5536 | ||
|  | 75efd9921d | ||
|  | 9219bd4723 | ||
|  | 73526be2ac | ||
|  | b827efca2c | ||
|  | 6b7a4c8a23 | ||
|  | 47f6239268 | ||
|  | 0d6f65b469 | ||
|  | be915aed94 | ||
|  | ce938bb4a5 | ||
|  | 15db563e8d | ||
|  | 82a538c750 | ||
|  | 84ca8ebbd9 | ||
|  | 774ebf4fa8 | ||
|  | eb1326c56a | ||
|  | 10cdb4360e | 
| @@ -108,7 +108,7 @@ dependencies { | |||||||
|     compile 'net.i2p.crypto:eddsa:0.3.0' |     compile 'net.i2p.crypto:eddsa:0.3.0' | ||||||
|  |  | ||||||
|     // LDAP connector |     // LDAP connector | ||||||
|     compile 'org.apache.directory.api:api-all:1.0.0' |     compile 'org.apache.directory.api:api-all:1.0.3' | ||||||
|  |  | ||||||
|     // DNS lookups |     // DNS lookups | ||||||
|     compile 'dnsjava:dnsjava:2.1.9' |     compile 'dnsjava:dnsjava:2.1.9' | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ Default values: | |||||||
| ```.yaml | ```.yaml | ||||||
| matrix: | matrix: | ||||||
|   v1: true   # deprecated |   v1: true   # deprecated | ||||||
|   v2: true |   v2: false | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| To disable change value to `false`. | To disable change value to `false`. | ||||||
| @@ -19,8 +19,14 @@ matrix: | |||||||
| ``` | ``` | ||||||
| NOTE: Riot Web version 1.5.5 and below checks the v1 for backward compatibility. | NOTE: Riot Web version 1.5.5 and below checks the v1 for backward compatibility. | ||||||
|  |  | ||||||
|  | NOTE: v2 disabled by default in order to preserve backward compatibility. | ||||||
|  |  | ||||||
| ## Terms | ## Terms | ||||||
|  |  | ||||||
|  | ###### Requires: No.  | ||||||
|  |  | ||||||
|  | Administrator can omit terms configuration. In this case the terms checking will be disabled. | ||||||
|  |  | ||||||
| Example: | Example: | ||||||
| ```.yaml | ```.yaml | ||||||
| policy: | policy: | ||||||
| @@ -45,7 +51,7 @@ Where: | |||||||
| - `version` -- the terms version. | - `version` -- the terms version. | ||||||
| - `lang` -- the term language. | - `lang` -- the term language. | ||||||
| - `name` -- the name of the term. | - `name` -- the name of the term. | ||||||
| - `url` -- the url of the term. | - `url` -- the url of the term. Might be any url (i.e. from another host) for a html page. | ||||||
| - `regexp` -- regexp patterns for API which should be available only after accepting the terms. | - `regexp` -- regexp patterns for API which should be available only after accepting the terms. | ||||||
|  |  | ||||||
| API will be checks for accepted terms only with authorization. | API will be checks for accepted terms only with authorization. | ||||||
| @@ -72,6 +78,10 @@ There is only one exception: [`POST /_matrix/identity/v2/terms`](https://matrix. | |||||||
|  |  | ||||||
| Hashes and the pepper updates together according to the `rotationPolicy`. | Hashes and the pepper updates together according to the `rotationPolicy`. | ||||||
|  |  | ||||||
|  | ###### Requires: No.  | ||||||
|  |  | ||||||
|  | In case the `none` algorithms ma1sd will be lookup using the v1 bulk API. | ||||||
|  |  | ||||||
| ```.yaml | ```.yaml | ||||||
| hashing: | hashing: | ||||||
|   enabled: true # enable or disable the hash lookup MSC2140 (default is false) |   enabled: true # enable or disable the hash lookup MSC2140 (default is false) | ||||||
| @@ -100,7 +110,7 @@ For sql provider (i.e. for the `synapseSql`): | |||||||
| ```.yaml | ```.yaml | ||||||
| synapseSql: | synapseSql: | ||||||
|   lookup: |   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: | For general sql provider: | ||||||
| @@ -126,5 +136,11 @@ exec: | |||||||
|     hashEnabled: true # enable the hash lookup (defaults is false) |     hashEnabled: true # enable the hash lookup (defaults is false) | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | For ldap providers: | ||||||
|  | ```.yaml | ||||||
|  | ldap: | ||||||
|  |   lookup: true | ||||||
|  | ``` | ||||||
|  |  | ||||||
| NOTE: Federation requests work only with `none` algorithms. | NOTE: Federation requests work only with `none` algorithms. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -58,7 +58,53 @@ Commonly the `server.publicUrl` should be the same value as the `trusted_third_p | |||||||
|  |  | ||||||
| ## Storage | ## Storage | ||||||
| ### SQLite | ### SQLite | ||||||
| `storage.provider.sqlite.database`: Absolute location of the SQLite database | ```yaml | ||||||
|  | storage: | ||||||
|  |   backend: sqlite # default | ||||||
|  |   provider: | ||||||
|  |     sqlite: | ||||||
|  |       database: /var/lib/ma1sd/store.db #  Absolute location of the SQLite database | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Postgresql | ||||||
|  | ```yaml | ||||||
|  | storage: | ||||||
|  |   backend: postgresql | ||||||
|  |   provider: | ||||||
|  |     postgresql: | ||||||
|  |       database: //localhost:5432/ma1sd | ||||||
|  |       username: ma1sd | ||||||
|  |       password: secret_password | ||||||
|  | ``` | ||||||
|  | See [the migration instruction](migration-to-postgresql.md) from sqlite to postgresql | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Logging | ||||||
|  | ```yaml | ||||||
|  | logging: | ||||||
|  |   root: error     # default level for all loggers (apps and thirdparty libraries) | ||||||
|  |   app: info       # log level only for the ma1sd | ||||||
|  |   requests: false # log request and response | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Possible value: `trace`, `debug`, `info`, `warn`, `error`, `off`. | ||||||
|  |  | ||||||
|  | Default value for root level: `info`. | ||||||
|  |  | ||||||
|  | Value for app level can be specified via `MA1SD_LOG_LEVEL` environment variable, configuration or start options. | ||||||
|  |  | ||||||
|  | Default value for app level: `info`. | ||||||
|  |  | ||||||
|  | | start option | equivalent configuration | | ||||||
|  | | --- | --- | | ||||||
|  | |  | app: info | | ||||||
|  | | -v | app: debug | | ||||||
|  | | -vv | app: trace | | ||||||
|  |  | ||||||
|  | #### WARNING | ||||||
|  |  | ||||||
|  | The setting `logging.requests` *MUST NOT* be used in production due it prints full unmasked request and response into the log and can be cause of the data leak.  | ||||||
|  | This setting can be used only to testing and debugging errors. | ||||||
|  |  | ||||||
| ## Identity stores | ## Identity stores | ||||||
| See the [Identity stores](stores/README.md) for specific configuration | See the [Identity stores](stores/README.md) for specific configuration | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Identity | # 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) | - [Lookups](#lookups) | ||||||
| - [Invitations](#invitations) | - [Invitations](#invitations) | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								docs/migration-to-postgresql.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								docs/migration-to-postgresql.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | # Migration from sqlite to postgresql | ||||||
|  |  | ||||||
|  | Starting from the version 2.3.0 ma1sd support postgresql for internal storage in addition to sqlite (parameters `storage.backend`). | ||||||
|  |  | ||||||
|  | #### Migration steps | ||||||
|  |  | ||||||
|  | 1. create the postgresql database and user for ma1sd storage | ||||||
|  | 2. create a backup for sqlite storage (default location: /var/lib/ma1sd/store.db) | ||||||
|  | 3. migrate data from sqlite to postgresql | ||||||
|  | 4. change ma1sd configuration to use the postgresql | ||||||
|  |  | ||||||
|  | For data migration is it possible to use https://pgloader.io tool. | ||||||
|  |  | ||||||
|  | Example of the migration command: | ||||||
|  | ```shell script | ||||||
|  | pgloader --with "quote identifiers" /path/to/store.db pgsql://ma1sd_user:ma1sd_password@host:port/database | ||||||
|  | ``` | ||||||
|  | or (short version for database on localhost) | ||||||
|  | ```shell script | ||||||
|  | pgloader --with "quote identifiers" /path/to/store.db pgsql://ma1sd_user:ma1sd_password@localhost/ma1sd | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | An option `--with "quote identifies"` used to create case sensitive tables. | ||||||
|  | ma1sd_user - postgresql user for ma1sd. | ||||||
|  | ma1sd_password - password of the postgresql user. | ||||||
|  | host - postgresql host | ||||||
|  | post - database port (default 5432) | ||||||
|  | database - database name. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Configuration example for postgresql storage: | ||||||
|  | ```yaml | ||||||
|  | storage: | ||||||
|  |   backend: postgresql | ||||||
|  |   provider: | ||||||
|  |     postgresql: | ||||||
|  |       database: '//localhost/ma1sd' # or full variant //192.168.1.100:5432/ma1sd_database | ||||||
|  |       username: 'ma1sd_user' | ||||||
|  |       password: 'ma1sd_password' | ||||||
|  | ``` | ||||||
|  |  | ||||||
| @@ -89,7 +89,7 @@ ldap: | |||||||
| #### 3PIDs | #### 3PIDs | ||||||
| You can also change the attribute lists for 3PID, like email or phone numbers. | You can also change the attribute lists for 3PID, like email or phone numbers. | ||||||
|  |  | ||||||
| The following example would overwrite the [default list of attributes](../../src/main/java/io/kamax/ma1sd/config/ldap/LdapConfig.java#L64) | The following example would overwrite the [default list of attributes](../../src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java#L64) | ||||||
| for emails and phone number: | for emails and phone number: | ||||||
| ```yaml | ```yaml | ||||||
| ldap: | ldap: | ||||||
|   | |||||||
| @@ -136,7 +136,7 @@ sql: | |||||||
|  |  | ||||||
| ``` | ``` | ||||||
| For the `role` query, `type` can be used to tell ma1sd how to inject the User ID in the query: | 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. | - `mxid` will use the ID as-is. | ||||||
|  |  | ||||||
| On each query, the first parameter `?` is set as a string with the corresponding ID format. | On each query, the first parameter `?` is set as a string with the corresponding ID format. | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ | |||||||
| matrix: | matrix: | ||||||
|   domain: '' |   domain: '' | ||||||
|   v1: true   # deprecated |   v1: true   # deprecated | ||||||
|   v2: true   # MSC2140 API v2 |   v2: true   # MSC2140 API v2. Riot require enabled V2 API. | ||||||
|  |  | ||||||
|  |  | ||||||
| ################ | ################ | ||||||
| @@ -51,10 +51,39 @@ key: | |||||||
| #  - /var/lib/ma1sd/store.db | #  - /var/lib/ma1sd/store.db | ||||||
| # | # | ||||||
| storage: | storage: | ||||||
|  | # backend: sqlite # or postgresql | ||||||
|   provider: |   provider: | ||||||
|     sqlite: |     sqlite: | ||||||
|       database: '/path/to/ma1sd.db' |       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 # | # Identity Stores # | ||||||
| @@ -115,36 +144,61 @@ threepid: | |||||||
|  |  | ||||||
| #### MSC2134 (hash lookup) | #### MSC2134 (hash lookup) | ||||||
|  |  | ||||||
| hashing: | #hashing: | ||||||
|   enabled: false # enable or disable the hash lookup MSC2140 (default is false) | #  enabled: false # enable or disable the hash lookup MSC2140 (default is false) | ||||||
|   pepperLength: 20 # length of the pepper value (default is 20) | #  pepperLength: 20 # length of the pepper value (default is 20) | ||||||
|   rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating | #  rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating | ||||||
|   hashStorageType: sql # or `in_memory` where the hashes will be stored | #  hashStorageType: sql # or `in_memory` where the hashes will be stored | ||||||
|   algorithms: | #  algorithms: | ||||||
|     - none   # the same as v1 bulk lookup | #    - none   # the same as v1 bulk lookup | ||||||
|     - sha256 # hash the 3PID and pepper. | #    - sha256 # hash the 3PID and pepper. | ||||||
|   delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s) | #  delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s) | ||||||
|   requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10) | #  requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10) | ||||||
|  |  | ||||||
| ### hash lookup for synapseSql provider. | ### hash lookup for synapseSql provider. | ||||||
| # synapseSql: | # synapseSql: | ||||||
| #   lookup: | #   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 | ||||||
|  | #   connection: | ||||||
|  | #     host: 'ldap.domain.tld' | ||||||
|  | #     port: 389 | ||||||
|  | #     bindDn: 'cn=admin,dc=domain,dc=tld' | ||||||
|  | #     bindPassword: 'Secret' | ||||||
|  | #     baseDNs: | ||||||
|  | #       - 'dc=domain,dc=tld' | ||||||
|  | #   attribute: | ||||||
|  | #     uid: | ||||||
|  | #       type: 'uid' # or mxid | ||||||
|  | #       value: 'cn' | ||||||
|  | #     name: 'displayName' | ||||||
|  | #   identity: | ||||||
|  | #     filter: '(objectClass=inetOrgPerson)' | ||||||
|  |  | ||||||
| #### MSC2140 (Terms) | #### MSC2140 (Terms) | ||||||
| policy: | #policy: | ||||||
|   policies: | #  policies: | ||||||
|     term_name: # term name | #    term_name: # term name | ||||||
|       version: 1.0 # version | #      version: 1.0 # version | ||||||
|       terms: | #      terms: | ||||||
|         en:  # lang | #        en:  # lang | ||||||
|           name: term name en  # localized name | #          name: term name en  # localized name | ||||||
|           url: https://ma1sd.host.tld/term_en.html  # localized url | #          url: https://ma1sd.host.tld/term_en.html  # localized url | ||||||
|         fe:  # lang | #        fe:  # lang | ||||||
|           name: term name fr  # localized name | #          name: term name fr  # localized name | ||||||
|           url: https://ma1sd.host.tld/term_fr.html  # localized url | #          url: https://ma1sd.host.tld/term_fr.html  # localized url | ||||||
|       regexp: | #      regexp: | ||||||
|         - '/_matrix/identity/v2/account.*' | #        - '/_matrix/identity/v2/account.*' | ||||||
|         - '/_matrix/identity/v2/hash_details' | #        - '/_matrix/identity/v2/hash_details' | ||||||
|         - '/_matrix/identity/v2/lookup' | #        - '/_matrix/identity/v2/lookup' | ||||||
|  | # | ||||||
|  |  | ||||||
|  | # logging: | ||||||
|  | #   root: error     # default level for all loggers (apps and thirdparty libraries) | ||||||
|  | #   app: info       # log level only for the ma1sd | ||||||
|  | #   requests: false # or true to dump full requests and responses | ||||||
|   | |||||||
| @@ -23,11 +23,13 @@ package io.kamax.mxisd; | |||||||
| import io.kamax.mxisd.config.MatrixConfig; | import io.kamax.mxisd.config.MatrixConfig; | ||||||
| import io.kamax.mxisd.config.MxisdConfig; | import io.kamax.mxisd.config.MxisdConfig; | ||||||
| import io.kamax.mxisd.config.PolicyConfig; | 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.ApiHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.AuthorizationHandler; | import io.kamax.mxisd.http.undertow.handler.AuthorizationHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.CheckTermsHandler; | import io.kamax.mxisd.http.undertow.handler.CheckTermsHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.InternalInfoHandler; | import io.kamax.mxisd.http.undertow.handler.InternalInfoHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.OptionsHandler; | 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.SaneHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.as.v1.AsNotFoundHandler; | import io.kamax.mxisd.http.undertow.handler.as.v1.AsNotFoundHandler; | ||||||
| import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler; | import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler; | ||||||
| @@ -99,35 +101,35 @@ public class HttpMxisd { | |||||||
|     public void start() { |     public void start() { | ||||||
|         m.start(); |         m.start(); | ||||||
|  |  | ||||||
|         HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs())); |         HttpHandler asUserHandler = sane(new AsUserHandler(m.getAs())); | ||||||
|         HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs())); |         HttpHandler asTxnHandler = sane(new AsTransactionHandler(m.getAs())); | ||||||
|         HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs())); |         HttpHandler asNotFoundHandler = sane(new AsNotFoundHandler(m.getAs())); | ||||||
|  |  | ||||||
|         final RoutingHandler handler = Handlers.routing() |         final RoutingHandler handler = Handlers.routing() | ||||||
|             .add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler())) |             .add("OPTIONS", "/**", sane(new OptionsHandler())) | ||||||
|  |  | ||||||
|             // Status endpoints |             // Status endpoints | ||||||
|             .get(StatusHandler.Path, SaneHandler.around(new StatusHandler())) |             .get(StatusHandler.Path, sane(new StatusHandler())) | ||||||
|             .get(VersionHandler.Path, SaneHandler.around(new VersionHandler())) |             .get(VersionHandler.Path, sane(new VersionHandler())) | ||||||
|  |  | ||||||
|             // Authentication endpoints |             // Authentication endpoints | ||||||
|             .get(LoginHandler.Path, SaneHandler.around(new LoginGetHandler(m.getAuth(), m.getHttpClient()))) |             .get(LoginHandler.Path, sane(new LoginGetHandler(m.getAuth(), m.getHttpClient()))) | ||||||
|             .post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth()))) |             .post(LoginHandler.Path, sane(new LoginPostHandler(m.getAuth()))) | ||||||
|             .post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth()))) |             .post(RestAuthHandler.Path, sane(new RestAuthHandler(m.getAuth()))) | ||||||
|  |  | ||||||
|             // Directory endpoints |             // Directory endpoints | ||||||
|             .post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory()))) |             .post(UserDirectorySearchHandler.Path, sane(new UserDirectorySearchHandler(m.getDirectory()))) | ||||||
|  |  | ||||||
|             // Profile endpoints |             // Profile endpoints | ||||||
|             .get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile()))) |             .get(ProfileHandler.Path, sane(new ProfileHandler(m.getProfile()))) | ||||||
|             .get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile()))) |             .get(InternalProfileHandler.Path, sane(new InternalProfileHandler(m.getProfile()))) | ||||||
|  |  | ||||||
|             // Registration endpoints |             // Registration endpoints | ||||||
|             .post(Register3pidRequestTokenHandler.Path, |             .post(Register3pidRequestTokenHandler.Path, | ||||||
|                 SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient()))) |                 sane(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient()))) | ||||||
|  |  | ||||||
|             // Invite endpoints |             // Invite endpoints | ||||||
|             .post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite()))) |             .post(RoomInviteHandler.Path, sane(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite()))) | ||||||
|  |  | ||||||
|             // Application Service endpoints |             // Application Service endpoints | ||||||
|             .get(AsUserHandler.Path, asUserHandler) |             .get(AsUserHandler.Path, asUserHandler) | ||||||
| @@ -139,13 +141,14 @@ public class HttpMxisd { | |||||||
|             .put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint |             .put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint | ||||||
|  |  | ||||||
|             // Banned endpoints |             // Banned endpoints | ||||||
|             .get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler())); |             .get(InternalInfoHandler.Path, sane(new InternalInfoHandler())); | ||||||
|         keyEndpoints(handler); |         keyEndpoints(handler); | ||||||
|         identityEndpoints(handler); |         identityEndpoints(handler); | ||||||
|         termsEndpoints(handler); |         termsEndpoints(handler); | ||||||
|         hashEndpoints(handler); |         hashEndpoints(handler); | ||||||
|         accountEndpoints(handler); |         accountEndpoints(handler); | ||||||
|         httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build(); |         ServerConfig serverConfig = m.getConfig().getServer(); | ||||||
|  |         httpSrv = Undertow.builder().addHttpListener(serverConfig.getPort(), serverConfig.getHostname()).setHandler(handler).build(); | ||||||
|  |  | ||||||
|         httpSrv.start(); |         httpSrv.start(); | ||||||
|     } |     } | ||||||
| @@ -189,23 +192,32 @@ public class HttpMxisd { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void accountEndpoints(RoutingHandler routingHandler) { |     private void accountEndpoints(RoutingHandler routingHandler) { | ||||||
|         routingHandler.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr()))); |         MatrixConfig matrixConfig = m.getConfig().getMatrix(); | ||||||
|         wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountGetUserInfoHandler(m.getAccMgr())), |         if (matrixConfig.isV2()) { | ||||||
|  |             routingHandler.post(AccountRegisterHandler.Path, sane(new AccountRegisterHandler(m.getAccMgr()))); | ||||||
|  |             wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new AccountGetUserInfoHandler(m.getAccMgr()), | ||||||
|                 AccountGetUserInfoHandler.Path, true); |                 AccountGetUserInfoHandler.Path, true); | ||||||
|         wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountLogoutHandler(m.getAccMgr())), |             wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new AccountLogoutHandler(m.getAccMgr()), | ||||||
|                 AccountLogoutHandler.Path, true); |                 AccountLogoutHandler.Path, true); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void termsEndpoints(RoutingHandler routingHandler) { |     private void termsEndpoints(RoutingHandler routingHandler) { | ||||||
|         routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy())); |         MatrixConfig matrixConfig = m.getConfig().getMatrix(); | ||||||
|  |         if (matrixConfig.isV2()) { | ||||||
|  |             routingHandler.get(GetTermsHandler.PATH, sane(new GetTermsHandler(m.getConfig().getPolicy()))); | ||||||
|             routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr()))); |             routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr()))); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void hashEndpoints(RoutingHandler routingHandler) { |     private void hashEndpoints(RoutingHandler routingHandler) { | ||||||
|         wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())), |         MatrixConfig matrixConfig = m.getConfig().getMatrix(); | ||||||
|  |         if (matrixConfig.isV2()) { | ||||||
|  |             wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, new HashDetailsHandler(m.getHashManager()), | ||||||
|                 HashDetailsHandler.PATH, true); |                 HashDetailsHandler.PATH, true); | ||||||
|             wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST, |             wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST, | ||||||
|             sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true); |                 new HashLookupHandler(m.getIdentity(), m.getHashManager()), HashLookupHandler.Path, true); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { |     private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { | ||||||
| @@ -218,11 +230,11 @@ public class HttpMxisd { | |||||||
|                                HttpHandler httpHandler) { |                                HttpHandler httpHandler) { | ||||||
|         MatrixConfig matrixConfig = m.getConfig().getMatrix(); |         MatrixConfig matrixConfig = m.getConfig().getMatrix(); | ||||||
|         if (matrixConfig.isV1()) { |         if (matrixConfig.isV1()) { | ||||||
|             routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), httpHandler); |             routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), sane(httpHandler)); | ||||||
|         } |         } | ||||||
|         if (matrixConfig.isV2()) { |         if (matrixConfig.isV2()) { | ||||||
|             String path = apiHandler.getPath(IdentityServiceAPI.V2); |             wrapWithTokenAndAuthorizationHandlers(routingHandler, method, httpHandler, apiHandler.getPath(IdentityServiceAPI.V2), | ||||||
|             wrapWithTokenAndAuthorizationHandlers(routingHandler, method, httpHandler, path, useAuthorization); |                 useAuthorization); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -236,7 +248,7 @@ public class HttpMxisd { | |||||||
|         } else { |         } else { | ||||||
|             wrappedHandler = httpHandler; |             wrappedHandler = httpHandler; | ||||||
|         } |         } | ||||||
|         routingHandler.add(method, url, wrappedHandler); |         routingHandler.add(method, url, sane(wrappedHandler)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @NotNull |     @NotNull | ||||||
| @@ -256,6 +268,11 @@ public class HttpMxisd { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HttpHandler sane(HttpHandler httpHandler) { |     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,6 +27,7 @@ import io.kamax.mxisd.auth.AuthProviders; | |||||||
| import io.kamax.mxisd.backend.IdentityStoreSupplier; | import io.kamax.mxisd.backend.IdentityStoreSupplier; | ||||||
| import io.kamax.mxisd.backend.sql.synapse.Synapse; | import io.kamax.mxisd.backend.sql.synapse.Synapse; | ||||||
| import io.kamax.mxisd.config.MxisdConfig; | import io.kamax.mxisd.config.MxisdConfig; | ||||||
|  | import io.kamax.mxisd.config.StorageConfig; | ||||||
| import io.kamax.mxisd.crypto.CryptoFactory; | import io.kamax.mxisd.crypto.CryptoFactory; | ||||||
| import io.kamax.mxisd.crypto.KeyManager; | import io.kamax.mxisd.crypto.KeyManager; | ||||||
| import io.kamax.mxisd.crypto.SignatureManager; | import io.kamax.mxisd.crypto.SignatureManager; | ||||||
| @@ -66,7 +67,7 @@ public class Mxisd { | |||||||
|     public static final String Version = StringUtils.defaultIfBlank(Mxisd.class.getPackage().getImplementationVersion(), "UNKNOWN"); |     public static final String Version = StringUtils.defaultIfBlank(Mxisd.class.getPackage().getImplementationVersion(), "UNKNOWN"); | ||||||
|     public static final String Agent = Name + "/" + Version; |     public static final String Agent = Name + "/" + Version; | ||||||
|  |  | ||||||
|     private MxisdConfig cfg; |     private final MxisdConfig cfg; | ||||||
|  |  | ||||||
|     private CloseableHttpClient httpClient; |     private CloseableHttpClient httpClient; | ||||||
|     private IRemoteIdentityServerFetcher srvFetcher; |     private IRemoteIdentityServerFetcher srvFetcher; | ||||||
| @@ -109,7 +110,10 @@ public class Mxisd { | |||||||
|         IdentityServerUtils.setHttpClient(httpClient); |         IdentityServerUtils.setHttpClient(httpClient); | ||||||
|         srvFetcher = new RemoteIdentityServerFetcher(httpClient); |         srvFetcher = new RemoteIdentityServerFetcher(httpClient); | ||||||
|  |  | ||||||
|         store = new OrmLiteSqlStorage(cfg); |         StorageConfig.BackendEnum storageBackend = cfg.getStorage().getBackend(); | ||||||
|  |         StorageConfig.Provider storageProvider = cfg.getStorage().getProvider(); | ||||||
|  |         store = new OrmLiteSqlStorage(storageBackend, storageProvider); | ||||||
|  |  | ||||||
|         keyMgr = CryptoFactory.getKeyManager(cfg.getKey()); |         keyMgr = CryptoFactory.getKeyManager(cfg.getKey()); | ||||||
|         signMgr = CryptoFactory.getSignatureManager(cfg, keyMgr); |         signMgr = CryptoFactory.getSignatureManager(cfg, keyMgr); | ||||||
|         clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite()); |         clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite()); | ||||||
|   | |||||||
| @@ -44,28 +44,43 @@ public class MxisdStandaloneExec { | |||||||
|         try { |         try { | ||||||
|             MxisdConfig cfg = null; |             MxisdConfig cfg = null; | ||||||
|             Iterator<String> argsIt = Arrays.asList(args).iterator(); |             Iterator<String> argsIt = Arrays.asList(args).iterator(); | ||||||
|  |             boolean dump = false; | ||||||
|  |             boolean exit = false; | ||||||
|             while (argsIt.hasNext()) { |             while (argsIt.hasNext()) { | ||||||
|                 String arg = argsIt.next(); |                 String arg = argsIt.next(); | ||||||
|                 if (StringUtils.equalsAny(arg, "-h", "--help", "-?", "--usage")) { |                 switch (arg) { | ||||||
|  |                     case "-h": | ||||||
|  |                     case "--help": | ||||||
|  |                     case "-?": | ||||||
|  |                     case "--usage": | ||||||
|                         System.out.println("Available arguments:" + System.lineSeparator()); |                         System.out.println("Available arguments:" + System.lineSeparator()); | ||||||
|                         System.out.println("  -h, --help       Show this help message"); |                         System.out.println("  -h, --help       Show this help message"); | ||||||
|                         System.out.println("  --version        Print the version then exit"); |                         System.out.println("  --version        Print the version then exit"); | ||||||
|                         System.out.println("  -c, --config     Set the configuration file location"); |                         System.out.println("  -c, --config     Set the configuration file location"); | ||||||
|                         System.out.println("  -v               Increase log level (log more info)"); |                         System.out.println("  -v               Increase log level (log more info)"); | ||||||
|                         System.out.println("  -vv              Further increase log level"); |                         System.out.println("  -vv              Further increase log level"); | ||||||
|  |                         System.out.println("  --dump           Dump the full ma1sd configuration"); | ||||||
|  |                         System.out.println("  --dump-and-exit  Dump the full ma1sd configuration and exit"); | ||||||
|                         System.out.println(" "); |                         System.out.println(" "); | ||||||
|                         System.exit(0); |                         System.exit(0); | ||||||
|                 } else if (StringUtils.equals(arg, "-v")) { |                         return; | ||||||
|  |                     case "-v": | ||||||
|                         System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "debug"); |                         System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "debug"); | ||||||
|                 } else if (StringUtils.equals(arg, "-vv")) { |                         break; | ||||||
|  |                     case "-vv": | ||||||
|                         System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "trace"); |                         System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", "trace"); | ||||||
|                 } else if (StringUtils.equalsAny(arg, "-c", "--config")) { |                         break; | ||||||
|  |                     case "-c": | ||||||
|  |                     case "--config": | ||||||
|                         String cfgFile = argsIt.next(); |                         String cfgFile = argsIt.next(); | ||||||
|                         cfg = YamlConfigLoader.loadFromFile(cfgFile); |                         cfg = YamlConfigLoader.loadFromFile(cfgFile); | ||||||
|                 } else if (StringUtils.equals("--version", arg)) { |                         break; | ||||||
|                     System.out.println(Mxisd.Version); |                     case "--dump-and-exit": | ||||||
|                     System.exit(0); |                         exit = true; | ||||||
|                 } else { |                     case "--dump": | ||||||
|  |                         dump = true; | ||||||
|  |                         break; | ||||||
|  |                     default: | ||||||
|                         System.err.println("Invalid argument: " + arg); |                         System.err.println("Invalid argument: " + arg); | ||||||
|                         System.err.println("Try '--help' for available arguments"); |                         System.err.println("Try '--help' for available arguments"); | ||||||
|                         System.exit(1); |                         System.exit(1); | ||||||
| @@ -76,6 +91,13 @@ public class MxisdStandaloneExec { | |||||||
|                 cfg = YamlConfigLoader.tryLoadFromFile("ma1sd.yaml").orElseGet(MxisdConfig::new); |                 cfg = YamlConfigLoader.tryLoadFromFile("ma1sd.yaml").orElseGet(MxisdConfig::new); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (dump) { | ||||||
|  |                 YamlConfigLoader.dumpConfig(cfg); | ||||||
|  |                 if (exit) { | ||||||
|  |                     System.exit(0); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             log.info("ma1sd starting"); |             log.info("ma1sd starting"); | ||||||
|             log.info("Version: {}", Mxisd.Version); |             log.info("Version: {}", Mxisd.Version); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -144,7 +144,13 @@ public class MembershipEventProcessor implements EventTypeProcessor { | |||||||
|                 .collect(Collectors.toList()); |                 .collect(Collectors.toList()); | ||||||
|         log.info("Found {} email(s) in identity store for {}", tpids.size(), inviteeId); |         log.info("Found {} email(s) in identity store for {}", tpids.size(), inviteeId); | ||||||
|  |  | ||||||
|         for (_ThreePid tpid : tpids) { |         log.info("Removing duplicates from identity store"); | ||||||
|  |         List<_ThreePid> uniqueTpids = tpids.stream() | ||||||
|  |                 .distinct() | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |         log.info("There are {} unique email(s) in identity store for {}", uniqueTpids.size(), inviteeId);         | ||||||
|  |  | ||||||
|  |         for (_ThreePid tpid : uniqueTpids) { | ||||||
|             log.info("Found Email to notify about room invitation: {}", tpid.getAddress()); |             log.info("Found Email to notify about room invitation: {}", tpid.getAddress()); | ||||||
|             Map<String, String> properties = new HashMap<>(); |             Map<String, String> properties = new HashMap<>(); | ||||||
|             profiler.getDisplayName(sender).ifPresent(name -> properties.put("sender_display_name", name)); |             profiler.getDisplayName(sender).ifPresent(name -> properties.put("sender_display_name", name)); | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import io.kamax.mxisd.exception.BadRequestException; | |||||||
| import io.kamax.mxisd.exception.InvalidCredentialsException; | import io.kamax.mxisd.exception.InvalidCredentialsException; | ||||||
| import io.kamax.mxisd.exception.NotFoundException; | import io.kamax.mxisd.exception.NotFoundException; | ||||||
| import io.kamax.mxisd.matrix.HomeserverFederationResolver; | import io.kamax.mxisd.matrix.HomeserverFederationResolver; | ||||||
|  | import io.kamax.mxisd.matrix.HomeserverVerifier; | ||||||
| import io.kamax.mxisd.storage.IStorage; | import io.kamax.mxisd.storage.IStorage; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.AccountDao; | import io.kamax.mxisd.storage.ormlite.dao.AccountDao; | ||||||
| import org.apache.http.HttpStatus; | import org.apache.http.HttpStatus; | ||||||
| @@ -22,18 +23,10 @@ import org.slf4j.Logger; | |||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.security.cert.Certificate; |  | ||||||
| import java.security.cert.CertificateParsingException; |  | ||||||
| import java.security.cert.X509Certificate; |  | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| import javax.net.ssl.HostnameVerifier; |  | ||||||
| import javax.net.ssl.SSLPeerUnverifiedException; |  | ||||||
| import javax.net.ssl.SSLSession; |  | ||||||
|  |  | ||||||
| public class AccountManager { | public class AccountManager { | ||||||
|  |  | ||||||
| @@ -80,7 +73,7 @@ public class AccountManager { | |||||||
|             homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); |             homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); | ||||||
|         String userId; |         String userId; | ||||||
|         try (CloseableHttpClient httpClient = HttpClients.custom() |         try (CloseableHttpClient httpClient = HttpClients.custom() | ||||||
|             .setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) { |             .setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) { | ||||||
|             try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { |             try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { | ||||||
|                 int statusCode = response.getStatusLine().getStatusCode(); |                 int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|                 if (statusCode == HttpStatus.SC_OK) { |                 if (statusCode == HttpStatus.SC_OK) { | ||||||
| @@ -170,74 +163,4 @@ public class AccountManager { | |||||||
|     public MatrixConfig getMatrixConfig() { |     public MatrixConfig getMatrixConfig() { | ||||||
|         return matrixConfig; |         return matrixConfig; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static class MatrixHostnameVerifier implements HostnameVerifier { |  | ||||||
|  |  | ||||||
|         private static final String ALT_DNS_NAME_TYPE = "2"; |  | ||||||
|         private static final String ALT_IP_ADDRESS_TYPE = "7"; |  | ||||||
|  |  | ||||||
|         private final String matrixHostname; |  | ||||||
|  |  | ||||||
|         public MatrixHostnameVerifier(String matrixHostname) { |  | ||||||
|             this.matrixHostname = matrixHostname; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         @Override |  | ||||||
|         public boolean verify(String hostname, SSLSession session) { |  | ||||||
|             try { |  | ||||||
|                 Certificate peerCertificate = session.getPeerCertificates()[0]; |  | ||||||
|                 if (peerCertificate instanceof X509Certificate) { |  | ||||||
|                     X509Certificate x509Certificate = (X509Certificate) peerCertificate; |  | ||||||
|                     if (x509Certificate.getSubjectAlternativeNames() == null) { |  | ||||||
|                         return false; |  | ||||||
|                     } |  | ||||||
|                     for (String altSubjectName : getAltSubjectNames(x509Certificate)) { |  | ||||||
|                         if (match(altSubjectName)) { |  | ||||||
|                             return true; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } catch (SSLPeerUnverifiedException | CertificateParsingException e) { |  | ||||||
|                 LOGGER.error("Unable to check remote host", e); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private List<String> getAltSubjectNames(X509Certificate x509Certificate) { |  | ||||||
|             List<String> subjectNames = new ArrayList<>(); |  | ||||||
|             try { |  | ||||||
|                 for (List<?> subjectAlternativeNames : x509Certificate.getSubjectAlternativeNames()) { |  | ||||||
|                     if (subjectAlternativeNames == null |  | ||||||
|                         || subjectAlternativeNames.size() < 2 |  | ||||||
|                         || subjectAlternativeNames.get(0) == null |  | ||||||
|                         || subjectAlternativeNames.get(1) == null) { |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
|                     String subjectType = subjectAlternativeNames.get(0).toString(); |  | ||||||
|                     switch (subjectType) { |  | ||||||
|                         case ALT_DNS_NAME_TYPE: |  | ||||||
|                         case ALT_IP_ADDRESS_TYPE: |  | ||||||
|                             subjectNames.add(subjectAlternativeNames.get(1).toString()); |  | ||||||
|                             break; |  | ||||||
|                         default: |  | ||||||
|                             LOGGER.trace("Unusable subject type: " + subjectType); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } catch (CertificateParsingException e) { |  | ||||||
|                 LOGGER.error("Unable to parse the certificate", e); |  | ||||||
|                 return Collections.emptyList(); |  | ||||||
|             } |  | ||||||
|             return subjectNames; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private boolean match(String altSubjectName) { |  | ||||||
|             if (altSubjectName.startsWith("*.")) { |  | ||||||
|                 return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase()); |  | ||||||
|             } else { |  | ||||||
|                 return matrixHostname.equalsIgnoreCase(altSubjectName); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -162,6 +162,7 @@ public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvid | |||||||
|             log.info("No match were found for {}", mxid); |             log.info("No match were found for {}", mxid); | ||||||
|             return BackendAuthResult.failure(); |             return BackendAuthResult.failure(); | ||||||
|         } catch (LdapException | IOException | CursorException e) { |         } catch (LdapException | IOException | CursorException e) { | ||||||
|  |             log.error("Unable to invoke query request: ", e); | ||||||
|             throw new InternalServerError(e); |             throw new InternalServerError(e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -41,7 +41,10 @@ import org.slf4j.LoggerFactory; | |||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
| public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider { | public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider { | ||||||
|  |  | ||||||
| @@ -137,4 +140,65 @@ public class LdapThreePidProvider extends LdapBackend implements IThreePidProvid | |||||||
|         return mappingsFound; |         return mappingsFound; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private List<String> getAttributes() { | ||||||
|  |         final List<String> attributes = getCfg().getAttribute().getThreepid().values().stream().flatMap(List::stream) | ||||||
|  |             .collect(Collectors.toList()); | ||||||
|  |         attributes.add(getUidAtt()); | ||||||
|  |         return attributes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Optional<String> getAttributeValue(Entry entry, List<String> attributes) { | ||||||
|  |         return attributes.stream() | ||||||
|  |             .map(attr -> getAttribute(entry, attr)) | ||||||
|  |             .filter(Objects::nonNull) | ||||||
|  |             .filter(Optional::isPresent) | ||||||
|  |             .map(Optional::get) | ||||||
|  |             .findFirst(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public Iterable<ThreePidMapping> populateHashes() { | ||||||
|  |         List<ThreePidMapping> result = new ArrayList<>(); | ||||||
|  |         if (!getCfg().getIdentity().isLookup()) { | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         String filter = getCfg().getIdentity().getFilter(); | ||||||
|  |  | ||||||
|  |         try (LdapConnection conn = getConn()) { | ||||||
|  |             bind(conn); | ||||||
|  |  | ||||||
|  |             log.debug("Query: {}", filter); | ||||||
|  |             List<String> attributes = getAttributes(); | ||||||
|  |             log.debug("Attributes: {}", GsonUtil.build().toJson(attributes)); | ||||||
|  |  | ||||||
|  |             for (String baseDN : getBaseDNs()) { | ||||||
|  |                 log.debug("Base DN: {}", baseDN); | ||||||
|  |  | ||||||
|  |                 try (EntryCursor cursor = conn.search(baseDN, filter, SearchScope.SUBTREE, attributes.toArray(new String[0]))) { | ||||||
|  |                     while (cursor.next()) { | ||||||
|  |                         Entry entry = cursor.get(); | ||||||
|  |                         log.info("Found possible match, DN: {}", entry.getDn().getName()); | ||||||
|  |                         Optional<String> mxid = getAttribute(entry, getUidAtt()); | ||||||
|  |                         if (!mxid.isPresent()) { | ||||||
|  |                             continue; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         for (Map.Entry<String, List<String>> attributeEntry : getCfg().getAttribute().getThreepid().entrySet()) { | ||||||
|  |                             String medium = attributeEntry.getKey(); | ||||||
|  |                             getAttributeValue(entry, attributeEntry.getValue()) | ||||||
|  |                                 .ifPresent(s -> result.add(new ThreePidMapping(medium, s, buildMatrixIdFromUid(mxid.get())))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (CursorLdapReferralException e) { | ||||||
|  |                     log.warn("3PID is only available via referral, skipping", e); | ||||||
|  |                 } catch (IOException | LdapException | CursorException e) { | ||||||
|  |                     log.error("Unable to fetch 3PID mappings", e); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (LdapException | IOException e) { | ||||||
|  |             log.error("Unable to fetch 3PID mappings", e); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -107,14 +107,16 @@ public abstract class SqlThreePidProvider implements IThreePidProvider { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public Iterable<ThreePidMapping> populateHashes() { |     public Iterable<ThreePidMapping> populateHashes() { | ||||||
|         if (StringUtils.isBlank(cfg.getLookup().getQuery())) { |         String query = cfg.getLookup().getQuery(); | ||||||
|  |         if (StringUtils.isBlank(query)) { | ||||||
|             log.warn("Lookup query not configured, skip."); |             log.warn("Lookup query not configured, skip."); | ||||||
|             return Collections.emptyList(); |             return Collections.emptyList(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         log.debug("Uses query to match users: {}", query); | ||||||
|         List<ThreePidMapping> result = new ArrayList<>(); |         List<ThreePidMapping> result = new ArrayList<>(); | ||||||
|         try (Connection connection = pool.get()) { |         try (Connection connection = pool.get()) { | ||||||
|             PreparedStatement statement = connection.prepareStatement(cfg.getLookup().getQuery()); |             PreparedStatement statement = connection.prepareStatement(query); | ||||||
|             try (ResultSet resultSet = statement.executeQuery()) { |             try (ResultSet resultSet = statement.executeQuery()) { | ||||||
|                 while (resultSet.next()) { |                 while (resultSet.next()) { | ||||||
|                     String mxid = resultSet.getString("mxid"); |                     String mxid = resultSet.getString("mxid"); | ||||||
|   | |||||||
| @@ -29,15 +29,19 @@ import java.util.Optional; | |||||||
|  |  | ||||||
| public class Synapse { | public class Synapse { | ||||||
|  |  | ||||||
|     private SqlConnectionPool pool; |     private final SqlConnectionPool pool; | ||||||
|  |     private final SynapseSqlProviderConfig providerConfig; | ||||||
|  |  | ||||||
|     public Synapse(SynapseSqlProviderConfig sqlCfg) { |     public Synapse(SynapseSqlProviderConfig sqlCfg) { | ||||||
|         this.pool = new SqlConnectionPool(sqlCfg); |         this.pool = new SqlConnectionPool(sqlCfg); | ||||||
|  |         providerConfig = sqlCfg; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Optional<String> getRoomName(String id) { |     public Optional<String> getRoomName(String id) { | ||||||
|  |         String query = providerConfig.isLegacyRoomNames() ? SynapseQueries.getLegacyRoomName() : SynapseQueries.getRoomName(); | ||||||
|  |  | ||||||
|         return pool.withConnFunction(conn -> { |         return pool.withConnFunction(conn -> { | ||||||
|             PreparedStatement stmt = conn.prepareStatement(SynapseQueries.getRoomName()); |             try (PreparedStatement stmt = conn.prepareStatement(query)) { | ||||||
|                 stmt.setString(1, id); |                 stmt.setString(1, id); | ||||||
|                 ResultSet rSet = stmt.executeQuery(); |                 ResultSet rSet = stmt.executeQuery(); | ||||||
|                 if (!rSet.next()) { |                 if (!rSet.next()) { | ||||||
| @@ -45,7 +49,7 @@ public class Synapse { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return Optional.ofNullable(rSet.getString(1)); |                 return Optional.ofNullable(rSet.getString(1)); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -72,7 +72,10 @@ public class SynapseQueries { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static String getRoomName() { |     public static String getRoomName() { | ||||||
|         return "select r.name from room_names r, events e, (select r1.room_id,max(e1.origin_server_ts) ts from room_names r1, events e1 where r1.event_id = e1.event_id group by r1.room_id) rle where e.origin_server_ts = rle.ts and r.event_id = e.event_id and r.room_id = ?"; |         return "select name from room_stats_state where room_id = ? limit 1"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static String getLegacyRoomName() { | ||||||
|  |         return "select r.name from room_names r, events e, (select r1.room_id,max(e1.origin_server_ts) ts from room_names r1, events e1 where r1.event_id = e1.event_id group by r1.room_id) rle where e.origin_server_ts = rle.ts and r.event_id = e.event_id and r.room_id = ?"; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package io.kamax.mxisd.config; | ||||||
|  |  | ||||||
|  | public interface DatabaseStorageConfig { | ||||||
|  |     String getDatabase(); | ||||||
|  | } | ||||||
| @@ -19,7 +19,7 @@ public class HashingConfig { | |||||||
|     private int requests = 10; |     private int requests = 10; | ||||||
|     private List<Algorithm> algorithms = new ArrayList<>(); |     private List<Algorithm> algorithms = new ArrayList<>(); | ||||||
|  |  | ||||||
|     public void build() { |     public void build(MatrixConfig matrixConfig) { | ||||||
|         if (isEnabled()) { |         if (isEnabled()) { | ||||||
|             LOGGER.info("--- Hash configuration ---"); |             LOGGER.info("--- Hash configuration ---"); | ||||||
|             LOGGER.info("   Pepper length: {}", getPepperLength()); |             LOGGER.info("   Pepper length: {}", getPepperLength()); | ||||||
| @@ -35,6 +35,9 @@ public class HashingConfig { | |||||||
|             } |             } | ||||||
|             LOGGER.info("   Algorithms: {}", getAlgorithms()); |             LOGGER.info("   Algorithms: {}", getAlgorithms()); | ||||||
|         } else { |         } else { | ||||||
|  |             if (matrixConfig.isV2()) { | ||||||
|  |                 LOGGER.warn("V2 enabled without the hash configuration."); | ||||||
|  |             } | ||||||
|             LOGGER.info("Hash configuration disabled, used only `none` pepper."); |             LOGGER.info("Hash configuration disabled, used only `none` pepper."); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								src/main/java/io/kamax/mxisd/config/LoggingConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/main/java/io/kamax/mxisd/config/LoggingConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | package io.kamax.mxisd.config; | ||||||
|  |  | ||||||
|  | import org.apache.commons.lang3.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | public class LoggingConfig { | ||||||
|  |  | ||||||
|  |     private static final Logger LOGGER = LoggerFactory.getLogger("App"); | ||||||
|  |  | ||||||
|  |     private String root; | ||||||
|  |     private String app; | ||||||
|  |     private boolean requests = false; | ||||||
|  |  | ||||||
|  |     public String getRoot() { | ||||||
|  |         return root; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setRoot(String root) { | ||||||
|  |         this.root = root; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getApp() { | ||||||
|  |         return app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setApp(String app) { | ||||||
|  |         this.app = app; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isRequests() { | ||||||
|  |         return requests; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setRequests(boolean requests) { | ||||||
|  |         this.requests = requests; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void build() { | ||||||
|  |         LOGGER.info("Logging config:"); | ||||||
|  |         if (StringUtils.isNotBlank(getRoot())) { | ||||||
|  |             LOGGER.info("  Default log level: {}", getRoot()); | ||||||
|  |             System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", getRoot()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         String appLevel = System.getProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd"); | ||||||
|  |         if (StringUtils.isNotBlank(appLevel)) { | ||||||
|  |             LOGGER.info("  Logging level set by environment: {}", appLevel); | ||||||
|  |         } else if (StringUtils.isNotBlank(getApp())) { | ||||||
|  |             System.setProperty("org.slf4j.simpleLogger.log.io.kamax.mxisd", getApp()); | ||||||
|  |             LOGGER.info("  Logging level set by the configuration: {}", getApp()); | ||||||
|  |         } else { | ||||||
|  |             LOGGER.info("  Logging level hasn't set, use default"); | ||||||
|  |         } | ||||||
|  |         LOGGER.info("  Log requests: {}", isRequests()); | ||||||
|  |         if (isRequests()) { | ||||||
|  |             LOGGER.warn("  Request dumping enabled, use this only to debug purposes, don't use it in the production."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -64,7 +64,7 @@ public class MatrixConfig { | |||||||
|     private String domain; |     private String domain; | ||||||
|     private Identity identity = new Identity(); |     private Identity identity = new Identity(); | ||||||
|     private boolean v1 = true; |     private boolean v1 = true; | ||||||
|     private boolean v2 = true; |     private boolean v2 = false; | ||||||
|  |  | ||||||
|     public String getDomain() { |     public String getDomain() { | ||||||
|         return domain; |         return domain; | ||||||
|   | |||||||
| @@ -117,6 +117,7 @@ public class MxisdConfig { | |||||||
|     private WordpressConfig wordpress = new WordpressConfig(); |     private WordpressConfig wordpress = new WordpressConfig(); | ||||||
|     private PolicyConfig policy = new PolicyConfig(); |     private PolicyConfig policy = new PolicyConfig(); | ||||||
|     private HashingConfig hashing = new HashingConfig(); |     private HashingConfig hashing = new HashingConfig(); | ||||||
|  |     private LoggingConfig logging = new LoggingConfig(); | ||||||
|  |  | ||||||
|     public AppServiceConfig getAppsvc() { |     public AppServiceConfig getAppsvc() { | ||||||
|         return appsvc; |         return appsvc; | ||||||
| @@ -342,6 +343,14 @@ public class MxisdConfig { | |||||||
|         this.hashing = hashing; |         this.hashing = hashing; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public LoggingConfig getLogging() { | ||||||
|  |         return logging; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setLogging(LoggingConfig logging) { | ||||||
|  |         this.logging = logging; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public MxisdConfig inMemory() { |     public MxisdConfig inMemory() { | ||||||
|         getKey().setPath(":memory:"); |         getKey().setPath(":memory:"); | ||||||
|         getStorage().getProvider().getSqlite().setDatabase(":memory:"); |         getStorage().getProvider().getSqlite().setDatabase(":memory:"); | ||||||
| @@ -350,6 +359,8 @@ public class MxisdConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public MxisdConfig build() { |     public MxisdConfig build() { | ||||||
|  |         getLogging().build(); | ||||||
|  |  | ||||||
|         if (StringUtils.isBlank(getServer().getName())) { |         if (StringUtils.isBlank(getServer().getName())) { | ||||||
|             getServer().setName(getMatrix().getDomain()); |             getServer().setName(getMatrix().getDomain()); | ||||||
|             log.debug("server.name is empty, using matrix.domain"); |             log.debug("server.name is empty, using matrix.domain"); | ||||||
| @@ -359,6 +370,7 @@ public class MxisdConfig { | |||||||
|         getAuth().build(); |         getAuth().build(); | ||||||
|         getAccountConfig().build(); |         getAccountConfig().build(); | ||||||
|         getDirectory().build(); |         getDirectory().build(); | ||||||
|  |         getDns().build(); | ||||||
|         getExec().build(); |         getExec().build(); | ||||||
|         getFirebase().build(); |         getFirebase().build(); | ||||||
|         getForward().build(); |         getForward().build(); | ||||||
| @@ -381,7 +393,7 @@ public class MxisdConfig { | |||||||
|         getView().build(); |         getView().build(); | ||||||
|         getWordpress().build(); |         getWordpress().build(); | ||||||
|         getPolicy().build(); |         getPolicy().build(); | ||||||
|         getHashing().build(); |         getHashing().build(getMatrix()); | ||||||
|  |  | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								src/main/java/io/kamax/mxisd/config/PostgresqlStorageConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/main/java/io/kamax/mxisd/config/PostgresqlStorageConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | /* | ||||||
|  |  * mxisd - Matrix Identity Server Daemon | ||||||
|  |  * Copyright (C) 2017 Kamax Sarl | ||||||
|  |  * | ||||||
|  |  * https://www.kamax.io/ | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU Affero General Public License as | ||||||
|  |  * published by the Free Software Foundation, either version 3 of the | ||||||
|  |  * License, or (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU Affero General Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package io.kamax.mxisd.config; | ||||||
|  |  | ||||||
|  | public class PostgresqlStorageConfig implements DatabaseStorageConfig { | ||||||
|  |  | ||||||
|  |     private String database; | ||||||
|  |  | ||||||
|  |     private String username; | ||||||
|  |  | ||||||
|  |     private String password; | ||||||
|  |  | ||||||
|  |     private boolean pool; | ||||||
|  |  | ||||||
|  |     private int maxConnectionsFree = 1; | ||||||
|  |  | ||||||
|  |     private long maxConnectionAgeMillis = 60 * 60 * 1000; | ||||||
|  |  | ||||||
|  |     private long checkConnectionsEveryMillis = 30 * 1000; | ||||||
|  |  | ||||||
|  |     private boolean testBeforeGetFromPool = false; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getDatabase() { | ||||||
|  |         return database; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setDatabase(String database) { | ||||||
|  |         this.database = database; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getUsername() { | ||||||
|  |         return username; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setUsername(String username) { | ||||||
|  |         this.username = username; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getPassword() { | ||||||
|  |         return password; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setPassword(String password) { | ||||||
|  |         this.password = password; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isPool() { | ||||||
|  |         return pool; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setPool(boolean pool) { | ||||||
|  |         this.pool = pool; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public int getMaxConnectionsFree() { | ||||||
|  |         return maxConnectionsFree; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setMaxConnectionsFree(int maxConnectionsFree) { | ||||||
|  |         this.maxConnectionsFree = maxConnectionsFree; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long getMaxConnectionAgeMillis() { | ||||||
|  |         return maxConnectionAgeMillis; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setMaxConnectionAgeMillis(long maxConnectionAgeMillis) { | ||||||
|  |         this.maxConnectionAgeMillis = maxConnectionAgeMillis; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long getCheckConnectionsEveryMillis() { | ||||||
|  |         return checkConnectionsEveryMillis; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setCheckConnectionsEveryMillis(long checkConnectionsEveryMillis) { | ||||||
|  |         this.checkConnectionsEveryMillis = checkConnectionsEveryMillis; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isTestBeforeGetFromPool() { | ||||||
|  |         return testBeforeGetFromPool; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setTestBeforeGetFromPool(boolean testBeforeGetFromPool) { | ||||||
|  |         this.testBeforeGetFromPool = testBeforeGetFromPool; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,10 +20,11 @@ | |||||||
|  |  | ||||||
| package io.kamax.mxisd.config; | package io.kamax.mxisd.config; | ||||||
|  |  | ||||||
| public class SQLiteStorageConfig { | public class SQLiteStorageConfig implements DatabaseStorageConfig { | ||||||
|  |  | ||||||
|     private String database; |     private String database; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|     public String getDatabase() { |     public String getDatabase() { | ||||||
|         return database; |         return database; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ public class ServerConfig { | |||||||
|     private String name; |     private String name; | ||||||
|     private int port = 8090; |     private int port = 8090; | ||||||
|     private String publicUrl; |     private String publicUrl; | ||||||
|  |     private String hostname; | ||||||
|  |  | ||||||
|     public String getName() { |     public String getName() { | ||||||
|         return name; |         return name; | ||||||
| @@ -59,6 +60,14 @@ public class ServerConfig { | |||||||
|         this.publicUrl = publicUrl; |         this.publicUrl = publicUrl; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public String getHostname() { | ||||||
|  |         return hostname; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setHostname(String hostname) { | ||||||
|  |         this.hostname = hostname; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public void build() { |     public void build() { | ||||||
|         log.info("--- Server config ---"); |         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>")); |             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("Name: {}", getName()); | ||||||
|         log.info("Port: {}", getPort()); |         log.info("Port: {}", getPort()); | ||||||
|         log.info("Public URL: {}", getPublicUrl()); |         log.info("Public URL: {}", getPublicUrl()); | ||||||
|  |         log.info("Hostname: {}", getHostname()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,14 +21,21 @@ | |||||||
| package io.kamax.mxisd.config; | package io.kamax.mxisd.config; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.exception.ConfigurationException; | import io.kamax.mxisd.exception.ConfigurationException; | ||||||
| import org.apache.commons.lang.StringUtils; |  | ||||||
|  |  | ||||||
| public class StorageConfig { | public class StorageConfig { | ||||||
|  |  | ||||||
|  |     public enum BackendEnum { | ||||||
|  |         sqlite, | ||||||
|  |  | ||||||
|  |         postgresql | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static class Provider { |     public static class Provider { | ||||||
|  |  | ||||||
|         private SQLiteStorageConfig sqlite = new SQLiteStorageConfig(); |         private SQLiteStorageConfig sqlite = new SQLiteStorageConfig(); | ||||||
|  |  | ||||||
|  |         private PostgresqlStorageConfig postgresql = new PostgresqlStorageConfig(); | ||||||
|  |  | ||||||
|         public SQLiteStorageConfig getSqlite() { |         public SQLiteStorageConfig getSqlite() { | ||||||
|             return sqlite; |             return sqlite; | ||||||
|         } |         } | ||||||
| @@ -37,16 +44,23 @@ public class StorageConfig { | |||||||
|             this.sqlite = sqlite; |             this.sqlite = sqlite; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public PostgresqlStorageConfig getPostgresql() { | ||||||
|  |             return postgresql; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     private String backend = "sqlite"; |         public void setPostgresql(PostgresqlStorageConfig postgresql) { | ||||||
|  |             this.postgresql = postgresql; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private BackendEnum backend = BackendEnum.sqlite; // or postgresql | ||||||
|     private Provider provider = new Provider(); |     private Provider provider = new Provider(); | ||||||
|  |  | ||||||
|     public String getBackend() { |     public BackendEnum getBackend() { | ||||||
|         return backend; |         return backend; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setBackend(String backend) { |     public void setBackend(BackendEnum backend) { | ||||||
|         this.backend = backend; |         this.backend = backend; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -59,7 +73,7 @@ public class StorageConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void build() { |     public void build() { | ||||||
|         if (StringUtils.isBlank(getBackend())) { |         if (getBackend() == null) { | ||||||
|             throw new ConfigurationException("storage.backend"); |             throw new ConfigurationException("storage.backend"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -76,4 +76,14 @@ public class YamlConfigLoader { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static void dumpConfig(MxisdConfig cfg) { | ||||||
|  |         Representer rep = new Representer(); | ||||||
|  |         rep.getPropertyUtils().setBeanAccess(BeanAccess.FIELD); | ||||||
|  |         rep.getPropertyUtils().setAllowReadOnlyProperties(true); | ||||||
|  |         rep.getPropertyUtils().setSkipMissingProperties(true); | ||||||
|  |  | ||||||
|  |         Yaml yaml = new Yaml(new Constructor(MxisdConfig.class), rep); | ||||||
|  |         String dump = yaml.dump(cfg); | ||||||
|  |         log.info("Full configuration:\n{}", dump); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -233,6 +233,7 @@ public abstract class LdapConfig { | |||||||
|         private String filter; |         private String filter; | ||||||
|         private String token = "%3pid"; |         private String token = "%3pid"; | ||||||
|         private Map<String, String> medium = new HashMap<>(); |         private Map<String, String> medium = new HashMap<>(); | ||||||
|  |         private boolean lookup = false; | ||||||
|  |  | ||||||
|         public String getFilter() { |         public String getFilter() { | ||||||
|             return filter; |             return filter; | ||||||
| @@ -262,6 +263,13 @@ public abstract class LdapConfig { | |||||||
|             this.medium = medium; |             this.medium = medium; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public boolean isLookup() { | ||||||
|  |             return lookup; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void setLookup(boolean lookup) { | ||||||
|  |             this.lookup = lookup; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static class Profile { |     public static class Profile { | ||||||
|   | |||||||
| @@ -45,4 +45,21 @@ public class MemoryThreePid implements _ThreePid { | |||||||
|         this.address = address; |         this.address = address; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(Object o) { | ||||||
|  |         if (this == o) return true; | ||||||
|  |         if (o == null || getClass() != o.getClass()) return false; | ||||||
|  |  | ||||||
|  |         MemoryThreePid threePid = (MemoryThreePid) o; | ||||||
|  |  | ||||||
|  |         if (!medium.equals(threePid.medium)) return false; | ||||||
|  |         return address.equals(threePid.address); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int hashCode() { | ||||||
|  |         int result = medium.hashCode(); | ||||||
|  |         result = 31 * result + address.hashCode(); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -125,7 +125,7 @@ public abstract class SqlConfig { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static class Lookup { |     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() { |         public String getQuery() { | ||||||
|             return query; |             return query; | ||||||
| @@ -140,7 +140,7 @@ public abstract class SqlConfig { | |||||||
|  |  | ||||||
|         private Boolean enabled; |         private Boolean enabled; | ||||||
|         private String type = "mxid"; |         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<>(); |         private Map<String, String> medium = new HashMap<>(); | ||||||
|  |  | ||||||
|         public Boolean isEnabled() { |         public Boolean isEnabled() { | ||||||
|   | |||||||
| @@ -24,9 +24,23 @@ import io.kamax.mxisd.UserIdType; | |||||||
| import io.kamax.mxisd.backend.sql.synapse.SynapseQueries; | import io.kamax.mxisd.backend.sql.synapse.SynapseQueries; | ||||||
| import io.kamax.mxisd.config.sql.SqlConfig; | import io.kamax.mxisd.config.sql.SqlConfig; | ||||||
| import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| public class SynapseSqlProviderConfig extends SqlConfig { | public class SynapseSqlProviderConfig extends SqlConfig { | ||||||
|  |  | ||||||
|  |     private transient final Logger log = LoggerFactory.getLogger(SynapseSqlProviderConfig.class); | ||||||
|  |  | ||||||
|  |     private boolean legacyRoomNames = false; | ||||||
|  |  | ||||||
|  |     public boolean isLegacyRoomNames() { | ||||||
|  |         return legacyRoomNames; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setLegacyRoomNames(boolean legacyRoomNames) { | ||||||
|  |         this.legacyRoomNames = legacyRoomNames; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected String getProviderName() { |     protected String getProviderName() { | ||||||
|         return "Synapse SQL"; |         return "Synapse SQL"; | ||||||
| @@ -42,7 +56,7 @@ public class SynapseSqlProviderConfig extends SqlConfig { | |||||||
|  |  | ||||||
|         if (getIdentity().isEnabled() && StringUtils.isBlank(getIdentity().getType())) { |         if (getIdentity().isEnabled() && StringUtils.isBlank(getIdentity().getType())) { | ||||||
|             getIdentity().setType("mxid"); |             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()) { |         if (getProfile().isEnabled()) { | ||||||
| @@ -65,4 +79,12 @@ public class SynapseSqlProviderConfig extends SqlConfig { | |||||||
|         printConfig(); |         printConfig(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected void printConfig() { | ||||||
|  |         super.printConfig(); | ||||||
|  |  | ||||||
|  |         if (isEnabled()) { | ||||||
|  |             log.info("Use legacy room name query: {}", isLegacyRoomNames()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| package io.kamax.mxisd.hash; | package io.kamax.mxisd.hash; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.config.HashingConfig; | import io.kamax.mxisd.config.HashingConfig; | ||||||
|  | import io.kamax.mxisd.hash.engine.Engine; | ||||||
|  | import io.kamax.mxisd.hash.engine.HashEngine; | ||||||
|  | import io.kamax.mxisd.hash.engine.NoneEngine; | ||||||
| import io.kamax.mxisd.hash.rotation.HashRotationStrategy; | import io.kamax.mxisd.hash.rotation.HashRotationStrategy; | ||||||
| import io.kamax.mxisd.hash.rotation.NoOpRotationStrategy; | import io.kamax.mxisd.hash.rotation.NoOpRotationStrategy; | ||||||
| import io.kamax.mxisd.hash.rotation.RotationPerRequests; | import io.kamax.mxisd.hash.rotation.RotationPerRequests; | ||||||
| @@ -21,7 +24,7 @@ public class HashManager { | |||||||
|  |  | ||||||
|     private static final Logger LOGGER = LoggerFactory.getLogger(HashManager.class); |     private static final Logger LOGGER = LoggerFactory.getLogger(HashManager.class); | ||||||
|  |  | ||||||
|     private HashEngine hashEngine; |     private Engine engine; | ||||||
|     private HashRotationStrategy rotationStrategy; |     private HashRotationStrategy rotationStrategy; | ||||||
|     private HashStorage hashStorage; |     private HashStorage hashStorage; | ||||||
|     private HashingConfig config; |     private HashingConfig config; | ||||||
| @@ -32,7 +35,7 @@ public class HashManager { | |||||||
|         this.config = config; |         this.config = config; | ||||||
|         this.storage = storage; |         this.storage = storage; | ||||||
|         initStorage(); |         initStorage(); | ||||||
|         hashEngine = new HashEngine(providers, getHashStorage(), config); |         engine = config.isEnabled() ? new HashEngine(providers, getHashStorage(), config) : new NoneEngine(); | ||||||
|         initRotationStrategy(); |         initRotationStrategy(); | ||||||
|         configured.set(true); |         configured.set(true); | ||||||
|     } |     } | ||||||
| @@ -73,8 +76,8 @@ public class HashManager { | |||||||
|         this.rotationStrategy.register(getHashEngine()); |         this.rotationStrategy.register(getHashEngine()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public HashEngine getHashEngine() { |     public Engine getHashEngine() { | ||||||
|         return hashEngine; |         return engine; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public HashRotationStrategy getRotationStrategy() { |     public HashRotationStrategy getRotationStrategy() { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								src/main/java/io/kamax/mxisd/hash/engine/Engine.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/main/java/io/kamax/mxisd/hash/engine/Engine.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | package io.kamax.mxisd.hash.engine; | ||||||
|  |  | ||||||
|  | public interface Engine { | ||||||
|  |     void updateHashes(); | ||||||
|  |  | ||||||
|  |     String getPepper(); | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package io.kamax.mxisd.hash; | package io.kamax.mxisd.hash.engine; | ||||||
| 
 | 
 | ||||||
| import io.kamax.mxisd.config.HashingConfig; | import io.kamax.mxisd.config.HashingConfig; | ||||||
| import io.kamax.mxisd.hash.storage.HashStorage; | import io.kamax.mxisd.hash.storage.HashStorage; | ||||||
| @@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory; | |||||||
| import java.util.Base64; | import java.util.Base64; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| public class HashEngine { | public class HashEngine implements Engine { | ||||||
| 
 | 
 | ||||||
|     private static final Logger LOGGER = LoggerFactory.getLogger(HashEngine.class); |     private static final Logger LOGGER = LoggerFactory.getLogger(HashEngine.class); | ||||||
| 
 | 
 | ||||||
| @@ -28,6 +28,7 @@ public class HashEngine { | |||||||
|         this.config = config; |         this.config = config; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|     public void updateHashes() { |     public void updateHashes() { | ||||||
|         LOGGER.info("Start update hashes."); |         LOGGER.info("Start update hashes."); | ||||||
|         synchronized (hashStorage) { |         synchronized (hashStorage) { | ||||||
| @@ -35,7 +36,9 @@ public class HashEngine { | |||||||
|             hashStorage.clear(); |             hashStorage.clear(); | ||||||
|             for (IThreePidProvider provider : providers) { |             for (IThreePidProvider provider : providers) { | ||||||
|                 try { |                 try { | ||||||
|  |                     LOGGER.info("Populate hashes from the handler: {}", provider.getClass().getCanonicalName()); | ||||||
|                     for (ThreePidMapping pidMapping : provider.populateHashes()) { |                     for (ThreePidMapping pidMapping : provider.populateHashes()) { | ||||||
|  |                         LOGGER.debug("Found 3PID: {}", pidMapping); | ||||||
|                         hashStorage.add(pidMapping, hash(pidMapping)); |                         hashStorage.add(pidMapping, hash(pidMapping)); | ||||||
|                     } |                     } | ||||||
|                 } catch (Exception e) { |                 } catch (Exception e) { | ||||||
| @@ -46,6 +49,7 @@ public class HashEngine { | |||||||
|         LOGGER.info("Finish update hashes."); |         LOGGER.info("Finish update hashes."); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|     public String getPepper() { |     public String getPepper() { | ||||||
|         synchronized (hashStorage) { |         synchronized (hashStorage) { | ||||||
|             return pepper; |             return pepper; | ||||||
							
								
								
									
										19
									
								
								src/main/java/io/kamax/mxisd/hash/engine/NoneEngine.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/main/java/io/kamax/mxisd/hash/engine/NoneEngine.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | package io.kamax.mxisd.hash.engine; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | public class NoneEngine implements Engine { | ||||||
|  |  | ||||||
|  |     private static final Logger LOGGER = LoggerFactory.getLogger(NoneEngine.class); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void updateHashes() { | ||||||
|  |         LOGGER.info("Nothing to update."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getPepper() { | ||||||
|  |         return ""; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,12 +1,12 @@ | |||||||
| package io.kamax.mxisd.hash.rotation; | package io.kamax.mxisd.hash.rotation; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.hash.HashEngine; | import io.kamax.mxisd.hash.engine.Engine; | ||||||
|  |  | ||||||
| public interface HashRotationStrategy { | public interface HashRotationStrategy { | ||||||
|  |  | ||||||
|     void register(HashEngine hashEngine); |     void register(Engine engine); | ||||||
|  |  | ||||||
|     HashEngine getHashEngine(); |     Engine getHashEngine(); | ||||||
|  |  | ||||||
|     void newRequest(); |     void newRequest(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +1,19 @@ | |||||||
| package io.kamax.mxisd.hash.rotation; | package io.kamax.mxisd.hash.rotation; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.hash.HashEngine; | import io.kamax.mxisd.hash.engine.Engine; | ||||||
|  |  | ||||||
| public class NoOpRotationStrategy implements HashRotationStrategy { | public class NoOpRotationStrategy implements HashRotationStrategy { | ||||||
|  |  | ||||||
|     private HashEngine hashEngine; |     private Engine engine; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void register(HashEngine hashEngine) { |     public void register(Engine engine) { | ||||||
|         this.hashEngine = hashEngine; |         this.engine = engine; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public HashEngine getHashEngine() { |     public Engine getHashEngine() { | ||||||
|         return hashEngine; |         return engine; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| package io.kamax.mxisd.hash.rotation; | package io.kamax.mxisd.hash.rotation; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.hash.HashEngine; | import io.kamax.mxisd.hash.engine.Engine; | ||||||
|  |  | ||||||
| import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||||
|  |  | ||||||
| public class RotationPerRequests implements HashRotationStrategy { | public class RotationPerRequests implements HashRotationStrategy { | ||||||
|  |  | ||||||
|     private HashEngine hashEngine; |     private Engine engine; | ||||||
|     private final AtomicInteger counter = new AtomicInteger(0); |     private final AtomicInteger counter = new AtomicInteger(0); | ||||||
|     private final int barrier; |     private final int barrier; | ||||||
|  |  | ||||||
| @@ -15,14 +15,14 @@ public class RotationPerRequests implements HashRotationStrategy { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void register(HashEngine hashEngine) { |     public void register(Engine engine) { | ||||||
|         this.hashEngine = hashEngine; |         this.engine = engine; | ||||||
|         trigger(); |         trigger(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public HashEngine getHashEngine() { |     public Engine getHashEngine() { | ||||||
|         return hashEngine; |         return engine; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package io.kamax.mxisd.hash.rotation; | package io.kamax.mxisd.hash.rotation; | ||||||
|  |  | ||||||
| import io.kamax.mxisd.hash.HashEngine; | import io.kamax.mxisd.hash.engine.Engine; | ||||||
|  |  | ||||||
| import java.util.concurrent.Executors; | import java.util.concurrent.Executors; | ||||||
| import java.util.concurrent.ScheduledExecutorService; | import java.util.concurrent.ScheduledExecutorService; | ||||||
| @@ -9,7 +9,7 @@ import java.util.concurrent.TimeUnit; | |||||||
| public class TimeBasedRotation implements HashRotationStrategy { | public class TimeBasedRotation implements HashRotationStrategy { | ||||||
|  |  | ||||||
|     private final long delay; |     private final long delay; | ||||||
|     private HashEngine hashEngine; |     private Engine engine; | ||||||
|     private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); |     private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); | ||||||
|  |  | ||||||
|     public TimeBasedRotation(long delay) { |     public TimeBasedRotation(long delay) { | ||||||
| @@ -17,15 +17,15 @@ public class TimeBasedRotation implements HashRotationStrategy { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void register(HashEngine hashEngine) { |     public void register(Engine engine) { | ||||||
|         this.hashEngine = hashEngine; |         this.engine = engine; | ||||||
|         Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdown)); |         Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdown)); | ||||||
|         executorService.scheduleWithFixedDelay(this::trigger, 0, delay, TimeUnit.SECONDS); |         executorService.scheduleWithFixedDelay(this::trigger, 0, delay, TimeUnit.SECONDS); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public HashEngine getHashEngine() { |     public Engine getHashEngine() { | ||||||
|         return hashEngine; |         return engine; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package io.kamax.mxisd.http.undertow.conduit; | ||||||
|  |  | ||||||
|  | public interface ConduitWithDump { | ||||||
|  |     String dump(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,107 @@ | |||||||
|  | /* | ||||||
|  |  * JBoss, Home of Professional Open Source. | ||||||
|  |  * Copyright 2014 Red Hat, Inc., and individual contributors | ||||||
|  |  * as indicated by the @author tags. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  *  Unless required by applicable law or agreed to in writing, software | ||||||
|  |  *  distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  *  See the License for the specific language governing permissions and | ||||||
|  |  *  limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package io.kamax.mxisd.http.undertow.conduit; | ||||||
|  |  | ||||||
|  | import org.xnio.IoUtils; | ||||||
|  | import org.xnio.channels.StreamSourceChannel; | ||||||
|  | import org.xnio.conduits.AbstractStreamSinkConduit; | ||||||
|  | import org.xnio.conduits.ConduitWritableByteChannel; | ||||||
|  | import org.xnio.conduits.Conduits; | ||||||
|  | import org.xnio.conduits.StreamSinkConduit; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | import java.nio.channels.FileChannel; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.concurrent.CopyOnWriteArrayList; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Conduit that saves all the data that is written through it and can dump it to the console | ||||||
|  |  * <p> | ||||||
|  |  * Obviously this should not be used in production. | ||||||
|  |  * | ||||||
|  |  * @author Stuart Douglas | ||||||
|  |  */ | ||||||
|  | public class DebuggingStreamSinkConduit extends AbstractStreamSinkConduit<StreamSinkConduit> implements ConduitWithDump { | ||||||
|  |  | ||||||
|  |     private final List<byte[]> data = new CopyOnWriteArrayList<>(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Construct a new instance. | ||||||
|  |      * | ||||||
|  |      * @param next the delegate conduit to set | ||||||
|  |      */ | ||||||
|  |     public DebuggingStreamSinkConduit(StreamSinkConduit next) { | ||||||
|  |         super(next); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int write(ByteBuffer src) throws IOException { | ||||||
|  |         int pos = src.position(); | ||||||
|  |         int res = super.write(src); | ||||||
|  |         if (res > 0) { | ||||||
|  |             byte[] d = new byte[res]; | ||||||
|  |             for (int i = 0; i < res; ++i) { | ||||||
|  |                 d[i] = src.get(i + pos); | ||||||
|  |             } | ||||||
|  |             data.add(d); | ||||||
|  |         } | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long write(ByteBuffer[] dsts, int offs, int len) throws IOException { | ||||||
|  |         for (int i = offs; i < len; ++i) { | ||||||
|  |             if (dsts[i].hasRemaining()) { | ||||||
|  |                 return write(dsts[i]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { | ||||||
|  |         return src.transferTo(position, count, new ConduitWritableByteChannel(this)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { | ||||||
|  |         return IoUtils.transfer(source, count, throughBuffer, new ConduitWritableByteChannel(this)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int writeFinal(ByteBuffer src) throws IOException { | ||||||
|  |         return Conduits.writeFinalBasic(this, src); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { | ||||||
|  |         return Conduits.writeFinalBasic(this, srcs, offset, length); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String dump() { | ||||||
|  |         StringBuilder sb = new StringBuilder(); | ||||||
|  |         for (byte[] datum : data) { | ||||||
|  |             sb.append(new String(datum, StandardCharsets.UTF_8)); | ||||||
|  |         } | ||||||
|  |         return sb.toString(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | /* | ||||||
|  |  * JBoss, Home of Professional Open Source. | ||||||
|  |  * Copyright 2014 Red Hat, Inc., and individual contributors | ||||||
|  |  * as indicated by the @author tags. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  *  Unless required by applicable law or agreed to in writing, software | ||||||
|  |  *  distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  *  See the License for the specific language governing permissions and | ||||||
|  |  *  limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package io.kamax.mxisd.http.undertow.conduit; | ||||||
|  |  | ||||||
|  | import org.xnio.IoUtils; | ||||||
|  | import org.xnio.channels.StreamSinkChannel; | ||||||
|  | import org.xnio.conduits.AbstractStreamSourceConduit; | ||||||
|  | import org.xnio.conduits.ConduitReadableByteChannel; | ||||||
|  | import org.xnio.conduits.StreamSourceConduit; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | import java.nio.channels.FileChannel; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.concurrent.CopyOnWriteArrayList; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Conduit that saves all the data that is written through it and can dump it to the console | ||||||
|  |  * <p> | ||||||
|  |  * Obviously this should not be used in production. | ||||||
|  |  * | ||||||
|  |  * @author Stuart Douglas | ||||||
|  |  */ | ||||||
|  | public class DebuggingStreamSourceConduit extends AbstractStreamSourceConduit<StreamSourceConduit> implements ConduitWithDump { | ||||||
|  |  | ||||||
|  |     private final List<byte[]> data = new CopyOnWriteArrayList<>(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Construct a new instance. | ||||||
|  |      * | ||||||
|  |      * @param next the delegate conduit to set | ||||||
|  |      */ | ||||||
|  |     public DebuggingStreamSourceConduit(StreamSourceConduit next) { | ||||||
|  |         super(next); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long transferTo(final long position, final long count, final FileChannel target) throws IOException { | ||||||
|  |         return target.transferFrom(new ConduitReadableByteChannel(this), position, count); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public long transferTo(final long count, final ByteBuffer throughBuffer, final StreamSinkChannel target) throws IOException { | ||||||
|  |         return IoUtils.transfer(new ConduitReadableByteChannel(this), count, throughBuffer, target); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public int read(ByteBuffer dst) throws IOException { | ||||||
|  |         int pos = dst.position(); | ||||||
|  |         int res = super.read(dst); | ||||||
|  |         if (res > 0) { | ||||||
|  |             byte[] d = new byte[res]; | ||||||
|  |             for (int i = 0; i < res; ++i) { | ||||||
|  |                 d[i] = dst.get(i + pos); | ||||||
|  |             } | ||||||
|  |             data.add(d); | ||||||
|  |         } | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public long read(ByteBuffer[] dsts, int offs, int len) throws IOException { | ||||||
|  |         for (int i = offs; i < len; ++i) { | ||||||
|  |             if (dsts[i].hasRemaining()) { | ||||||
|  |                 return read(dsts[i]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String dump() { | ||||||
|  |  | ||||||
|  |         StringBuilder sb = new StringBuilder(); | ||||||
|  |         for (byte[] datum : data) { | ||||||
|  |             sb.append(new String(datum, StandardCharsets.UTF_8)); | ||||||
|  |         } | ||||||
|  |         return sb.toString(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package io.kamax.mxisd.http.undertow.conduit; | ||||||
|  |  | ||||||
|  | import io.undertow.server.ConduitWrapper; | ||||||
|  | import io.undertow.server.HttpServerExchange; | ||||||
|  | import io.undertow.util.ConduitFactory; | ||||||
|  | import org.xnio.conduits.Conduit; | ||||||
|  |  | ||||||
|  | public abstract class LazyConduitWrapper<T extends Conduit> implements ConduitWrapper<T> { | ||||||
|  |  | ||||||
|  |     private T conduit = null; | ||||||
|  |  | ||||||
|  |     protected abstract T create(ConduitFactory<T> factory, HttpServerExchange exchange); | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public T wrap(ConduitFactory<T> factory, HttpServerExchange exchange) { | ||||||
|  |         conduit = create(factory, exchange); | ||||||
|  |         return conduit; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public T get() { | ||||||
|  |         return conduit; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -48,7 +48,7 @@ public class AccountRegisterHandler extends BasicHttpHandler { | |||||||
|  |  | ||||||
|         if (LOGGER.isInfoEnabled()) { |         if (LOGGER.isInfoEnabled()) { | ||||||
|             LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(), |             LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(), | ||||||
|                 new Date(openIdToken.getExpiresIn())); |                 new Date(System.currentTimeMillis() + openIdToken.getExpiresIn())); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         String token = accountManager.register(openIdToken); |         String token = accountManager.register(openIdToken); | ||||||
|   | |||||||
| @@ -31,6 +31,8 @@ public class HashDetailsHandler extends BasicHttpHandler { | |||||||
|             for (HashingConfig.Algorithm algorithm : config.getAlgorithms()) { |             for (HashingConfig.Algorithm algorithm : config.getAlgorithms()) { | ||||||
|                 algorithms.add(algorithm.name().toLowerCase()); |                 algorithms.add(algorithm.name().toLowerCase()); | ||||||
|             } |             } | ||||||
|  |         } else { | ||||||
|  |             algorithms.add(HashingConfig.Algorithm.none.name().toLowerCase()); | ||||||
|         } |         } | ||||||
|         response.add("algorithms", algorithms); |         response.add("algorithms", algorithms); | ||||||
|         return response; |         return response; | ||||||
|   | |||||||
| @@ -31,6 +31,8 @@ import io.kamax.mxisd.http.undertow.handler.ApiHandler; | |||||||
| import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler; | import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler; | ||||||
| import io.kamax.mxisd.lookup.BulkLookupRequest; | import io.kamax.mxisd.lookup.BulkLookupRequest; | ||||||
| import io.kamax.mxisd.lookup.HashLookupRequest; | import io.kamax.mxisd.lookup.HashLookupRequest; | ||||||
|  | import io.kamax.mxisd.lookup.SingleLookupReply; | ||||||
|  | import io.kamax.mxisd.lookup.SingleLookupRequest; | ||||||
| import io.kamax.mxisd.lookup.ThreePidMapping; | import io.kamax.mxisd.lookup.ThreePidMapping; | ||||||
| import io.kamax.mxisd.lookup.strategy.LookupStrategy; | import io.kamax.mxisd.lookup.strategy.LookupStrategy; | ||||||
| import io.undertow.server.HttpServerExchange; | import io.undertow.server.HttpServerExchange; | ||||||
| @@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory; | |||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Optional; | ||||||
|  |  | ||||||
| public class HashLookupHandler extends LookupHandler implements ApiHandler { | public class HashLookupHandler extends LookupHandler implements ApiHandler { | ||||||
|  |  | ||||||
| @@ -64,10 +67,6 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler { | |||||||
|         log.info("Got bulk lookup request from {} with client {} - Is recursive? {}", |         log.info("Got bulk lookup request from {} with client {} - Is recursive? {}", | ||||||
|             lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); |             lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); | ||||||
|  |  | ||||||
|         if (!hashManager.getConfig().isEnabled()) { |  | ||||||
|             throw new InvalidParamException(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!hashManager.getHashEngine().getPepper().equals(input.getPepper())) { |         if (!hashManager.getHashEngine().getPepper().equals(input.getPepper())) { | ||||||
|             throw new InvalidPepperException(); |             throw new InvalidPepperException(); | ||||||
|         } |         } | ||||||
| @@ -82,13 +81,26 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler { | |||||||
|             default: |             default: | ||||||
|                 throw new InvalidParamException(); |                 throw new InvalidParamException(); | ||||||
|         } |         } | ||||||
|  |         hashManager.getRotationStrategy().newRequest(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception { |     private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception { | ||||||
|         if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.none)) { |         if (hashManager.getConfig().isEnabled() && !hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.none)) { | ||||||
|             throw new InvalidParamException(); |             throw new InvalidParamException(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         ClientHashLookupAnswer answer = null; | ||||||
|  |         if (input.getAddresses() != null && input.getAddresses().size() > 0) { | ||||||
|  |             if (input.getAddresses().size() == 1) { | ||||||
|  |                 answer = noneSingleLookup(request, input); | ||||||
|  |             } else { | ||||||
|  |                 answer = noneBulkLookup(request, input); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         respondJson(exchange, answer != null ? answer : new ClientHashLookupAnswer()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private ClientHashLookupAnswer noneBulkLookup(HashLookupRequest request, ClientHashLookupRequest input) throws Exception { | ||||||
|         BulkLookupRequest bulkLookupRequest = new BulkLookupRequest(); |         BulkLookupRequest bulkLookupRequest = new BulkLookupRequest(); | ||||||
|         List<ThreePidMapping> mappings = new ArrayList<>(); |         List<ThreePidMapping> mappings = new ArrayList<>(); | ||||||
|         for (String address : input.getAddresses()) { |         for (String address : input.getAddresses()) { | ||||||
| @@ -107,7 +119,26 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler { | |||||||
|         } |         } | ||||||
|         log.info("Finished bulk lookup request from {}", request.getRequester()); |         log.info("Finished bulk lookup request from {}", request.getRequester()); | ||||||
|  |  | ||||||
|         respondJson(exchange, answer); |         return answer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private ClientHashLookupAnswer noneSingleLookup(HashLookupRequest request, ClientHashLookupRequest input) { | ||||||
|  |         SingleLookupRequest singleLookupRequest = new SingleLookupRequest(); | ||||||
|  |         String address = input.getAddresses().get(0); | ||||||
|  |         String[] parts = address.split(" "); | ||||||
|  |         singleLookupRequest.setThreePid(parts[0]); | ||||||
|  |         singleLookupRequest.setType(parts[1]); | ||||||
|  |  | ||||||
|  |         ClientHashLookupAnswer answer = new ClientHashLookupAnswer(); | ||||||
|  |  | ||||||
|  |         Optional<SingleLookupReply> singleLookupReply = strategy.find(singleLookupRequest); | ||||||
|  |         if (singleLookupReply.isPresent()) { | ||||||
|  |             SingleLookupReply reply = singleLookupReply.get(); | ||||||
|  |             answer.getMappings().put(address, reply.getMxid().toString()); | ||||||
|  |         } | ||||||
|  |         log.info("Finished single lookup request from {}", request.getRequester()); | ||||||
|  |  | ||||||
|  |         return answer; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) { |     private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) { | ||||||
|   | |||||||
| @@ -46,6 +46,4 @@ public interface LookupStrategy { | |||||||
|     Optional<SingleLookupReply> findRecursive(SingleLookupRequest request); |     Optional<SingleLookupReply> findRecursive(SingleLookupRequest request); | ||||||
|  |  | ||||||
|     CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest requests); |     CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest requests); | ||||||
|  |  | ||||||
|     CompletableFuture<List<ThreePidMapping>> find(HashLookupRequest request); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,17 +26,23 @@ import io.kamax.matrix.json.MatrixJson; | |||||||
| import io.kamax.mxisd.config.MxisdConfig; | import io.kamax.mxisd.config.MxisdConfig; | ||||||
| import io.kamax.mxisd.exception.ConfigurationException; | import io.kamax.mxisd.exception.ConfigurationException; | ||||||
| import io.kamax.mxisd.hash.HashManager; | import io.kamax.mxisd.hash.HashManager; | ||||||
| import io.kamax.mxisd.hash.storage.HashStorage; | import io.kamax.mxisd.lookup.ALookupRequest; | ||||||
| import io.kamax.mxisd.lookup.*; | import io.kamax.mxisd.lookup.BulkLookupRequest; | ||||||
|  | import io.kamax.mxisd.lookup.SingleLookupReply; | ||||||
|  | import io.kamax.mxisd.lookup.SingleLookupRequest; | ||||||
|  | import io.kamax.mxisd.lookup.ThreePidMapping; | ||||||
| import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; | import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; | ||||||
| import io.kamax.mxisd.lookup.provider.IThreePidProvider; | import io.kamax.mxisd.lookup.provider.IThreePidProvider; | ||||||
| import org.apache.commons.codec.digest.DigestUtils; | import org.apache.commons.codec.digest.DigestUtils; | ||||||
| import org.apache.commons.lang3.tuple.Pair; |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import java.net.UnknownHostException; | import java.net.UnknownHostException; | ||||||
| import java.util.*; | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  | import java.util.Optional; | ||||||
| import java.util.concurrent.CompletableFuture; | import java.util.concurrent.CompletableFuture; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| @@ -238,13 +244,4 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy { | |||||||
|         result.complete(mapFoundAll); |         result.complete(mapFoundAll); | ||||||
|         return bulkLookupInProgress.remove(payloadId); |         return bulkLookupInProgress.remove(payloadId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public CompletableFuture<List<ThreePidMapping>> find(HashLookupRequest request) { |  | ||||||
|         HashStorage hashStorage = hashManager.getHashStorage(); |  | ||||||
|         CompletableFuture<List<ThreePidMapping>> result = new CompletableFuture<>(); |  | ||||||
|         result.complete(hashStorage.find(request.getHashes()).stream().map(Pair::getValue).collect(Collectors.toList())); |  | ||||||
|         hashManager.getRotationStrategy().newRequest(); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -77,7 +77,8 @@ public class HomeserverVerifier implements HostnameVerifier { | |||||||
|  |  | ||||||
|     private boolean match(String altSubjectName) { |     private boolean match(String altSubjectName) { | ||||||
|         if (altSubjectName.startsWith("*.")) { |         if (altSubjectName.startsWith("*.")) { | ||||||
|             return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase()); |             String subjectNameWithoutMask = altSubjectName.substring(1); // remove wildcard | ||||||
|  |             return matrixHostname.toLowerCase().endsWith(subjectNameWithoutMask.toLowerCase()); | ||||||
|         } else { |         } else { | ||||||
|             return matrixHostname.equalsIgnoreCase(altSubjectName); |             return matrixHostname.equalsIgnoreCase(altSubjectName); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -23,13 +23,18 @@ package io.kamax.mxisd.storage.ormlite; | |||||||
| import com.j256.ormlite.dao.CloseableWrappedIterable; | import com.j256.ormlite.dao.CloseableWrappedIterable; | ||||||
| import com.j256.ormlite.dao.Dao; | import com.j256.ormlite.dao.Dao; | ||||||
| import com.j256.ormlite.dao.DaoManager; | 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.JdbcConnectionSource; | ||||||
|  | import com.j256.ormlite.jdbc.JdbcPooledConnectionSource; | ||||||
| import com.j256.ormlite.stmt.QueryBuilder; | import com.j256.ormlite.stmt.QueryBuilder; | ||||||
| import com.j256.ormlite.support.ConnectionSource; | import com.j256.ormlite.support.ConnectionSource; | ||||||
| import com.j256.ormlite.table.TableUtils; | import com.j256.ormlite.table.TableUtils; | ||||||
| import io.kamax.matrix.ThreePid; | import io.kamax.matrix.ThreePid; | ||||||
| import io.kamax.mxisd.config.MxisdConfig; |  | ||||||
| import io.kamax.mxisd.config.PolicyConfig; | 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.ConfigurationException; | ||||||
| import io.kamax.mxisd.exception.InternalServerError; | import io.kamax.mxisd.exception.InternalServerError; | ||||||
| import io.kamax.mxisd.exception.InvalidCredentialsException; | import io.kamax.mxisd.exception.InvalidCredentialsException; | ||||||
| @@ -39,6 +44,7 @@ import io.kamax.mxisd.storage.IStorage; | |||||||
| import io.kamax.mxisd.storage.dao.IThreePidSessionDao; | import io.kamax.mxisd.storage.dao.IThreePidSessionDao; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao; | import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.AccountDao; | import io.kamax.mxisd.storage.ormlite.dao.AccountDao; | ||||||
|  | import io.kamax.mxisd.storage.ormlite.dao.ChangelogDao; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.HashDao; | import io.kamax.mxisd.storage.ormlite.dao.HashDao; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO; | import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO; | ||||||
| import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao; | import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao; | ||||||
| @@ -46,12 +52,15 @@ import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO; | |||||||
| import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao; | import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao; | ||||||
| import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||||
| import org.apache.commons.lang3.tuple.Pair; | import org.apache.commons.lang3.tuple.Pair; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.sql.SQLException; | import java.sql.SQLException; | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| @@ -59,6 +68,8 @@ import java.util.stream.Collectors; | |||||||
|  |  | ||||||
| public class OrmLiteSqlStorage implements IStorage { | public class OrmLiteSqlStorage implements IStorage { | ||||||
|  |  | ||||||
|  |     private static final Logger LOGGER = LoggerFactory.getLogger(OrmLiteSqlStorage.class); | ||||||
|  |  | ||||||
|     @FunctionalInterface |     @FunctionalInterface | ||||||
|     private interface Getter<T> { |     private interface Getter<T> { | ||||||
|  |  | ||||||
| @@ -73,42 +84,148 @@ 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"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private Dao<ThreePidInviteIO, String> invDao; |     private Dao<ThreePidInviteIO, String> invDao; | ||||||
|     private Dao<HistoricalThreePidInviteIO, String> expInvDao; |     private Dao<HistoricalThreePidInviteIO, String> expInvDao; | ||||||
|     private Dao<ThreePidSessionDao, String> sessionDao; |     private Dao<ThreePidSessionDao, String> sessionDao; | ||||||
|     private Dao<ASTransactionDao, String> asTxnDao; |     private Dao<ASTransactionDao, String> asTxnDao; | ||||||
|     private Dao<AccountDao, String> accountDao; |     private Dao<AccountDao, String> accountDao; | ||||||
|     private Dao<AcceptedDao, String> acceptedDao; |     private Dao<AcceptedDao, Long> acceptedDao; | ||||||
|     private Dao<HashDao, String> hashDao; |     private Dao<HashDao, String> hashDao; | ||||||
|  |     private Dao<ChangelogDao, String> changelogDao; | ||||||
|  |     private StorageConfig.BackendEnum backend; | ||||||
|  |  | ||||||
|     public OrmLiteSqlStorage(MxisdConfig cfg) { |     public OrmLiteSqlStorage(StorageConfig.BackendEnum backend, StorageConfig.Provider provider) { | ||||||
|         this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase()); |         if (backend == null) { | ||||||
|  |             throw new ConfigurationException("storage.backend"); | ||||||
|         } |         } | ||||||
|  |         this.backend = backend; | ||||||
|  |  | ||||||
|     public OrmLiteSqlStorage(String backend, String path) { |         withCatcher(() -> { | ||||||
|         if (StringUtils.isBlank(backend)) { |             ConnectionSource connPool; | ||||||
|  |             switch (backend) { | ||||||
|  |                 case postgresql: | ||||||
|  |                     connPool = createPostgresqlConnection(provider.getPostgresql()); | ||||||
|  |                     break; | ||||||
|  |                 case sqlite: | ||||||
|  |                     connPool = createSqliteConnection(provider.getSqlite()); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|                     throw new ConfigurationException("storage.backend"); |                     throw new ConfigurationException("storage.backend"); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         if (StringUtils.isBlank(path)) { |             changelogDao = createDaoAndTable(connPool, ChangelogDao.class); | ||||||
|             throw new ConfigurationException("Storage destination cannot be empty"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         withCatcher(() -> { |  | ||||||
|             ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path); |  | ||||||
|             invDao = createDaoAndTable(connPool, ThreePidInviteIO.class); |             invDao = createDaoAndTable(connPool, ThreePidInviteIO.class); | ||||||
|             expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class); |             expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class); | ||||||
|             sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class); |             sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class); | ||||||
|             asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class); |             asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class); | ||||||
|             accountDao = createDaoAndTable(connPool, AccountDao.class); |             accountDao = createDaoAndTable(connPool, AccountDao.class); | ||||||
|             acceptedDao = createDaoAndTable(connPool, AcceptedDao.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.")); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void fixAcceptedDao(ConnectionSource connPool) throws SQLException { | ||||||
|  |         LOGGER.info("Migration: {}", Migrations.FIX_ACCEPTED_DAO); | ||||||
|  |         TableUtils.dropTable(acceptedDao, true); | ||||||
|  |         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 <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException { |     private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException { | ||||||
|  |         return createDaoAndTable(connPool, c, false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Workaround for https://github.com/j256/ormlite-core/issues/20. | ||||||
|  |      */ | ||||||
|  |     private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c, boolean workaround) throws SQLException { | ||||||
|  |         LOGGER.info("Create the dao: {}", c.getSimpleName()); | ||||||
|         Dao<V, K> dao = DaoManager.createDao(connPool, c); |         Dao<V, K> dao = DaoManager.createDao(connPool, c); | ||||||
|  |         if (workaround && StorageConfig.BackendEnum.postgresql.equals(backend)) { | ||||||
|  |             LOGGER.info("Workaround for postgresql on dao: {}", c.getSimpleName()); | ||||||
|  |             try { | ||||||
|  |                 dao.countOf(); | ||||||
|  |                 LOGGER.info("Table exists, do nothing"); | ||||||
|  |             } catch (SQLException e) { | ||||||
|  |                 LOGGER.info("Table doesn't exist, create"); | ||||||
|                 TableUtils.createTableIfNotExists(connPool, c); |                 TableUtils.createTableIfNotExists(connPool, c); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             TableUtils.createTableIfNotExists(connPool, c); | ||||||
|  |         } | ||||||
|         return dao; |         return dao; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,7 +26,10 @@ import com.j256.ormlite.table.DatabaseTable; | |||||||
| @DatabaseTable(tableName = "accepted") | @DatabaseTable(tableName = "accepted") | ||||||
| public class AcceptedDao { | public class AcceptedDao { | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false, id = true) |     @DatabaseField(generatedId = true) | ||||||
|  |     private Long id; | ||||||
|  |  | ||||||
|  |     @DatabaseField(canBeNull = false) | ||||||
|     private String url; |     private String url; | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false) |     @DatabaseField(canBeNull = false) | ||||||
| @@ -45,6 +48,14 @@ public class AcceptedDao { | |||||||
|         this.acceptedAt = acceptedAt; |         this.acceptedAt = acceptedAt; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public Long getId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setId(Long id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public String getUrl() { |     public String getUrl() { | ||||||
|         return url; |         return url; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | package io.kamax.mxisd.storage.ormlite.dao; | ||||||
|  |  | ||||||
|  | import com.j256.ormlite.field.DatabaseField; | ||||||
|  | import com.j256.ormlite.table.DatabaseTable; | ||||||
|  |  | ||||||
|  | import java.util.Date; | ||||||
|  |  | ||||||
|  | @DatabaseTable(tableName = "changelog") | ||||||
|  | public class ChangelogDao { | ||||||
|  |  | ||||||
|  |     @DatabaseField(id = true) | ||||||
|  |     private String id; | ||||||
|  |  | ||||||
|  |     @DatabaseField | ||||||
|  |     private Date createdAt; | ||||||
|  |  | ||||||
|  |     @DatabaseField | ||||||
|  |     private String comment; | ||||||
|  |  | ||||||
|  |     public ChangelogDao() { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public ChangelogDao(String id, Date createdAt, String comment) { | ||||||
|  |         this.id = id; | ||||||
|  |         this.createdAt = createdAt; | ||||||
|  |         this.comment = comment; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setId(String id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Date getCreatedAt() { | ||||||
|  |         return createdAt; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setCreatedAt(Date createdAt) { | ||||||
|  |         this.createdAt = createdAt; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public String getComment() { | ||||||
|  |         return comment; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setComment(String comment) { | ||||||
|  |         this.comment = comment; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,13 +6,16 @@ import com.j256.ormlite.table.DatabaseTable; | |||||||
| @DatabaseTable(tableName = "hashes") | @DatabaseTable(tableName = "hashes") | ||||||
| public class HashDao { | public class HashDao { | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false, id = true) |     @DatabaseField(generatedId = true) | ||||||
|  |     private Long id; | ||||||
|  |  | ||||||
|  |     @DatabaseField(canBeNull = false, uniqueCombo = true) | ||||||
|     private String mxid; |     private String mxid; | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false) |     @DatabaseField(canBeNull = false, uniqueCombo = true) | ||||||
|     private String medium; |     private String medium; | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false) |     @DatabaseField(canBeNull = false, uniqueCombo = true) | ||||||
|     private String address; |     private String address; | ||||||
|  |  | ||||||
|     @DatabaseField(canBeNull = false) |     @DatabaseField(canBeNull = false) | ||||||
| @@ -28,6 +31,14 @@ public class HashDao { | |||||||
|         this.hash = hash; |         this.hash = hash; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public Long getId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setId(Long id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public String getMxid() { |     public String getMxid() { | ||||||
|         return mxid; |         return mxid; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ | |||||||
|  |  | ||||||
| package io.kamax.mxisd.test.storage; | 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 io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage; | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
|  |  | ||||||
| @@ -29,14 +31,22 @@ public class OrmLiteSqlStorageTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     public void insertAsTxnDuplicate() { |     public void insertAsTxnDuplicate() { | ||||||
|         OrmLiteSqlStorage store = new OrmLiteSqlStorage("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(), "{}"); | ||||||
|         store.insertTransactionResult("mxisd", "2", Instant.now(), "{}"); |         store.insertTransactionResult("mxisd", "2", Instant.now(), "{}"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test(expected = RuntimeException.class) |     @Test(expected = RuntimeException.class) | ||||||
|     public void insertAsTxnSame() { |     public void insertAsTxnSame() { | ||||||
|         OrmLiteSqlStorage store = new OrmLiteSqlStorage("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(), "{}"); | ||||||
|         store.insertTransactionResult("mxisd", "1", Instant.now(), "{}"); |         store.insertTransactionResult("mxisd", "1", Instant.now(), "{}"); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user