Compare commits
588 Commits
v0.2.0-rc.
...
master
Author | SHA1 | Date | |
---|---|---|---|
0de8e247cb | |||
8d4f331880 | |||
7f1b77ce19 | |||
d4080bb2e5 | |||
0106364680 | |||
b1ef1b9aa1 | |||
84b4c9c3ed | |||
dedce04961 | |||
a4e99fcc79 | |||
d568678df5 | |||
31210f4e71 | |||
df70b65ac3 | |||
949bfc38f8 | |||
c5e5f643a2 | |||
5820156512 | |||
7cc4be7cd6 | |||
5e31ff9b69 | |||
d910472db5 | |||
ca56afad5d | |||
541aeee3c2 | |||
19257a0290 | |||
8d91e7d5a3 | |||
c0671acf60 | |||
0b8300c774 | |||
cac55d5f06 | |||
aa3aadbb53 | |||
da7fa9edd6 | |||
a1a21e8d60 | |||
dc2932cf51 | |||
bb84e32719 | |||
12aa40b160 | |||
9d4fddcb2d | |||
4654cff158 | |||
b1888fed96 | |||
d7e8b3d62a | |||
56bfdda18c | |||
fb3debfb49 | |||
49f812f867 | |||
de0a3152c3 | |||
cc5d047c3f | |||
350776df17 | |||
7b6560e9c8 | |||
4fd4fdac60 | |||
dfad9d9ce8 | |||
d2fc4e3bef | |||
4416c17216 | |||
f54ed462b1 | |||
7498dcf122 | |||
7e5665a56f | |||
f633e9d256 | |||
bdfe4a00a9 | |||
50d4f0fa30 | |||
bb190230b2 | |||
c9bafd9af2 | |||
3c9c8ca1b5 | |||
dacf96140c | |||
1d228d75e6 | |||
62530eda08 | |||
15c3e6b425 | |||
945a2dbdf5 | |||
d5cebf103d | |||
de14f80a40 | |||
7ed6db537d | |||
9d8d88527c | |||
a1c82878f2 | |||
ab95c8ee32 | |||
e50a49b198 | |||
4492888557 | |||
ed5407fc42 | |||
a2911f2ace | |||
e99578d168 | |||
d471ea71d7 | |||
56115df282 | |||
2e1194d216 | |||
ee001d543c | |||
a826eb319e | |||
6b557e9954 | |||
041a4d1b73 | |||
c1a1741bfb | |||
6e1058c484 | |||
5f6fb38485 | |||
c9046ffbd0 | |||
cf97c5a88f | |||
3d1a8c4495 | |||
46dc07ef07 | |||
d9ab2b8739 | |||
14032aaf50 | |||
73051c3d00 | |||
a3c8c4a8cf | |||
453751a39e | |||
ad23e91ece | |||
baabdfc2ef | |||
263e3260c9 | |||
a281bdbf77 | |||
338e7f4aa2 | |||
a9212ce73a | |||
f2e1fcb25f | |||
f5630bc4d9 | |||
dbccaecfd6 | |||
de0842d3a8 | |||
05424f14b8 | |||
2587e6be7c | |||
20062fe5bc | |||
0ec6462ff4 | |||
567c7bb972 | |||
1c6322f1c2 | |||
603cdd1340 | |||
a6164379c5 | |||
fc9d6b3a36 | |||
8b14664dc7 | |||
f0922d67df | |||
6e05b51fdc | |||
0999ef6309 | |||
cae00f4606 | |||
941ae697e7 | |||
fbf1ea8f5b | |||
4eef3ac40d | |||
e6c90aa00f | |||
ca306a3212 | |||
e8864afe02 | |||
f09ede8780 | |||
98146d3468 | |||
5f64b72a91 | |||
acb2b5a37d | |||
9f9c48aaa7 | |||
eb6e541e15 | |||
e45ac5a78a | |||
aebcb7b5e1 | |||
c45f95ce3f | |||
640fa8e9f1 | |||
0f3c37bf6a | |||
|
ae5864cd91 | ||
|
e456724caf | ||
|
ed9dcc4061 | ||
|
ea8e386939 | ||
|
e0ec887118 | ||
|
a71d32ba77 | ||
|
a0f6fe9b0d | ||
|
c25647156a | ||
|
e7c4c12a98 | ||
|
90b2b5301c | ||
|
0d93a26e6d | ||
|
c29fc0f0eb | ||
|
e421c851c9 | ||
|
5b2b45233a | ||
|
888f7a4209 | ||
|
0c301a49c7 | ||
|
1fda2dd3b7 | ||
|
c4a20efe5e | ||
|
fc45f1b090 | ||
|
1480507d76 | ||
|
a1ab1e8e0a | ||
|
1e5033b461 | ||
|
7323851c6e | ||
|
08db73e55b | ||
|
9fba20475b | ||
|
9af5fce014 | ||
|
9843e14c1a | ||
|
60e6f1e23c | ||
|
6cdbcc69c7 | ||
|
ed7c714738 | ||
|
a9d783192b | ||
|
2bb5a734d1 | ||
|
9aa5c4cca9 | ||
|
9c4faab5d8 | ||
|
53c4ffdc4e | ||
|
e4144e923a | ||
|
791361c10d | ||
|
7c94bd4744 | ||
|
4b5eecd7e7 | ||
|
a6968fb7e9 | ||
|
d4853b1154 | ||
|
89df4b2425 | ||
|
0f89121b98 | ||
|
8a40ca185b | ||
|
5baeb42623 | ||
|
072e5f66cb | ||
|
b2f41d689b | ||
|
9b4aff58c7 | ||
|
a20e41574d | ||
|
72977d65ae | ||
|
7555fff1a5 | ||
|
aed12e5536 | ||
|
75efd9921d | ||
|
9219bd4723 | ||
|
73526be2ac | ||
|
b827efca2c | ||
|
6b7a4c8a23 | ||
|
47f6239268 | ||
|
0d6f65b469 | ||
|
be915aed94 | ||
|
ce938bb4a5 | ||
|
15db563e8d | ||
|
82a538c750 | ||
|
84ca8ebbd9 | ||
|
774ebf4fa8 | ||
|
eb1326c56a | ||
|
10cdb4360e | ||
|
17ebc2a421 | ||
|
cbb9fced8d | ||
|
7509174611 | ||
|
51d9225dda | ||
|
6216113400 | ||
|
cb32441959 | ||
|
0ec4df2c06 | ||
|
86b880069b | ||
|
a97273fe77 | ||
|
f9daf4d58a | ||
|
9e4cabb69b | ||
|
0b81de3cd0 | ||
|
698a16ec17 | ||
|
619b70d860 | ||
|
494c9e3941 | ||
|
0786a6520f | ||
|
430136c391 | ||
|
eda4404335 | ||
|
c52034b18a | ||
|
8d346037b7 | ||
|
14ad4435bc | ||
|
94441d0446 | ||
|
b4776b50e2 | ||
|
2458b38b75 | ||
|
249e28a8b5 | ||
|
8ba8756871 | ||
|
ba9e2d6121 | ||
|
f042b82a50 | ||
|
59071177ad | ||
|
6450cd1f20 | ||
|
90bc244f3e | ||
|
6e52a509db | ||
|
5ca666981a | ||
|
43fe8b1aec | ||
|
a0270c7d01 | ||
|
703044d06a | ||
|
add6ed8fd9 | ||
|
baed894ff8 | ||
|
14e095a147 | ||
|
bc8795e940 | ||
|
5521c0c338 | ||
|
614b3440e2 | ||
|
d0fd9fb9b0 | ||
|
1232e9ce79 | ||
|
a47a983c10 | ||
|
fbb0d7c7ba | ||
|
f1dd309551 | ||
|
36f22e5ca6 | ||
|
a112a5e57c | ||
|
dbc764fe65 | ||
|
d5680b2dfe | ||
|
5aad4fb81e | ||
|
a1f64f5159 | ||
|
a96920f533 | ||
|
9ca2ebdad0 | ||
|
7f284ac71e | ||
|
4d488a3fc8 | ||
|
7ee3e19de4 | ||
|
608938c084 | ||
|
e6fec9199d | ||
|
c3262a9f25 | ||
|
136563c61a | ||
|
3043cd4e61 | ||
|
21d9d0fda1 | ||
|
a964b073bf | ||
|
f85345bc97 | ||
|
29603682e5 | ||
|
d54f1dcb88 | ||
|
92f10347d1 | ||
|
0298f66212 | ||
|
0ddd086bda | ||
|
544f8e59f0 | ||
|
917f87bf8c | ||
|
774795c203 | ||
|
27b2976e42 | ||
|
f16f184253 | ||
|
cd890d114a | ||
|
321ba1e325 | ||
|
c3ce0a17f6 | ||
|
0fcc0d9bb2 | ||
|
ce7f900543 | ||
|
c7c009f9af | ||
|
3b01663245 | ||
|
9cc601d582 | ||
|
e6272b1827 | ||
|
8243354f39 | ||
|
25968e0737 | ||
|
44a80461a0 | ||
|
85d9f9e704 | ||
|
6278301672 | ||
|
5ed0c66cfd | ||
|
ea58b6985a | ||
|
a44f781495 | ||
|
0d42ee695a | ||
|
f331af0941 | ||
|
e2c8a56135 | ||
|
a67c5d7ae1 | ||
|
80352070f1 | ||
|
39447b8b8b | ||
|
9d4680f55a | ||
|
d1ea0fbf0f | ||
|
ee21f051fb | ||
|
6cc17abf2c | ||
|
a7b5accd75 | ||
|
6bb0c93f57 | ||
|
9abdcc15ba | ||
|
eb903bf226 | ||
|
1cbb0a135b | ||
|
1587103c0a | ||
|
838d79ae15 | ||
|
96c47ecf76 | ||
|
c5cea933a4 | ||
|
57c7e4a91d | ||
|
1dce59a02e | ||
|
de840b9d00 | ||
|
53c85d2248 | ||
|
254dc5684f | ||
|
de92e98f7d | ||
|
d5f9137056 | ||
|
1307e3aa43 | ||
|
dfedde0df6 | ||
|
93bd7354c2 | ||
|
c302789898 | ||
|
96155c1876 | ||
|
95ee328281 | ||
|
72a1794cc3 | ||
|
37ddd0e588 | ||
|
4d63bba251 | ||
|
aadfae2965 | ||
|
2f7e5e4025 | ||
|
77dc75d383 | ||
|
f3b528d1ba | ||
|
91e5e08e70 | ||
|
acd8c7d7c5 | ||
|
249cc0ea92 | ||
|
99697d7c75 | ||
|
e133e120d7 | ||
|
e39d6bfa10 | ||
|
217bc423ed | ||
|
8f0654c34e | ||
|
8afdb3ed83 | ||
|
bd4ccbc5e5 | ||
|
6d1c6ed109 | ||
|
1619f5311c | ||
|
6fa36ea092 | ||
|
471e06536b | ||
|
3a6b75996c | ||
|
566e4f3137 | ||
|
a4c18dee5d | ||
|
8d6850d346 | ||
|
67bc18af7d | ||
|
5c660fdcaf | ||
|
fbbafeb769 | ||
|
559f6a7401 | ||
|
3bebb33147 | ||
|
3e240fe34d | ||
|
635f6fdbe7 | ||
|
4237eeb3b6 | ||
|
a0e91e7896 | ||
|
aab0b86646 | ||
|
3e22301af7 | ||
|
2b202323c0 | ||
|
4ec05f518e | ||
|
6da68298b0 | ||
|
aecaafdeca | ||
|
d885932f45 | ||
|
c689a3f161 | ||
|
7805112548 | ||
|
3e89f0bc5e | ||
|
c6b8f7d48e | ||
|
83377ebee0 | ||
|
2aa6e4d142 | ||
|
82a1a3df68 | ||
|
7ec11ba8cf | ||
|
9317c11434 | ||
|
b257a0275f | ||
|
2aaa04062f | ||
|
54c3014568 | ||
|
c3ca73f576 | ||
|
4185b644b7 | ||
|
ace5918342 | ||
|
7ad985fead | ||
|
6a376db322 | ||
|
950f7c931c | ||
|
d160a44509 | ||
|
05493da27c | ||
|
df44428a85 | ||
|
e6f9c30611 | ||
|
06b2c787d3 | ||
|
5645f69208 | ||
|
92cf5c6b21 | ||
|
ad1b91f370 | ||
|
e9c29f1c03 | ||
|
f13748abeb | ||
|
7208c7e456 | ||
|
8857f636d6 | ||
|
d9fc41e8c7 | ||
|
da08e0b4ad | ||
|
11fc8f08b0 | ||
|
af4d734105 | ||
|
0f4f5ac81b | ||
|
8c4ddd2e65 | ||
|
cb8049b54a | ||
|
99b7d9f27d | ||
|
ded5e3db5e | ||
|
b892d19023 | ||
|
026a2e82d9 | ||
|
b881f73798 | ||
|
99d793b5ed | ||
|
cb02f62b9d | ||
|
bd9161ec9b | ||
|
544cab816c | ||
|
cdb56aec1f | ||
|
407138e972 | ||
|
3eee4eaccf | ||
|
b3aefbed77 | ||
|
29017fbe1e | ||
|
843fa04f19 | ||
|
f7d1a300f1 | ||
|
f16eb264be | ||
|
f29014be1f | ||
|
20a4d8dd91 | ||
|
0c0feab0c0 | ||
|
dd313881db | ||
|
feb37112b2 | ||
|
1ab8a27fda | ||
|
deafc420a5 | ||
|
fce15f0e29 | ||
|
5b5893f407 | ||
|
f55d5fbc80 | ||
|
b613415dc4 | ||
|
0549d23d21 | ||
|
b493ccd479 | ||
|
03e72ba155 | ||
|
32a3444a9e | ||
|
78a25c21ba | ||
|
ef80f4aa30 | ||
|
1e413af019 | ||
|
a0f8af820e | ||
|
5ef145212a | ||
|
91ccb75fa1 | ||
|
ac6f549618 | ||
|
7f9c7aa76d | ||
|
02688942fd | ||
|
48668bcd92 | ||
|
a9627121fa | ||
|
3fc86465f8 | ||
|
d93b546e3c | ||
|
ea15f24d41 | ||
|
290a32d640 | ||
|
10f9126cb6 | ||
|
c3385b38dc | ||
|
61fec4aec7 | ||
|
1db76139a9 | ||
|
a27858082c | ||
|
ea08a80504 | ||
|
cb3130d365 | ||
|
7189a4b100 | ||
|
f71cdbf83e | ||
|
665a284f4b | ||
|
5e142eb41d | ||
|
9fede41904 | ||
|
5871bb6609 | ||
|
5dbaca643a | ||
|
bf9576f9c3 | ||
|
773f38d349 | ||
|
6a5a4b3c1c | ||
|
7fff2448a1 | ||
|
6571ff76b1 | ||
|
16690a0329 | ||
|
6ac593f0fa | ||
|
1581ab9e07 | ||
|
a1adca72e8 | ||
|
e2b3920840 | ||
|
aaa742f6d2 | ||
|
959feb686c | ||
|
d9c5c5056a | ||
|
83fafdcfeb | ||
|
e916ecd08b | ||
|
1461d8ef6c | ||
|
19c1214e4a | ||
|
b976f69c39 | ||
|
3675da4a0f | ||
|
077955d538 | ||
|
9af0cd3615 | ||
|
2bf68538c3 | ||
|
6c02e478d9 | ||
|
284da779f9 | ||
|
af161296b3 | ||
|
6317acd7fc | ||
|
30260af1f2 | ||
|
3b697e86ac | ||
|
b4f0645257 | ||
|
0e48edf86e | ||
|
7e92bfa474 | ||
|
851e0c9d94 | ||
|
ac1cbc4265 | ||
|
62711ee12e | ||
|
3954be2f08 | ||
|
640512eb27 | ||
|
40705b5d47 | ||
|
642d560ba9 | ||
|
b6e86f5b2e | ||
|
4a99ec5531 | ||
|
9079bb25cc | ||
|
88e86cd0d5 | ||
|
8662b3f39f | ||
|
d0aac5ac52 | ||
|
c702a34aab | ||
|
786e4a8f91 | ||
|
8d0b0edad2 | ||
|
88a37c52c0 | ||
|
52e4a65c3c | ||
|
69ecef0155 | ||
|
f7984bd36e | ||
|
f735b3b730 | ||
|
b6008a41f2 | ||
|
ed2d13decf | ||
|
4f3ecc19f3 | ||
|
c816217b22 | ||
|
182f3c4bc3 | ||
|
2e7b5d2a87 | ||
|
09208d55d7 | ||
|
05c76a657e | ||
|
f3bbc7c7c6 | ||
|
61addd297a | ||
|
1de0951733 | ||
|
d348ebd813 | ||
|
0499c10a2c | ||
|
13e248c71e | ||
|
d221b2c5de | ||
|
3a1900cbb2 | ||
|
9f1867a030 | ||
|
a061241291 | ||
|
fefa81e935 | ||
|
1e77bf43c6 | ||
|
c73bbf675e | ||
|
6c2e65ace5 | ||
|
33263d3cff | ||
|
af19fed6e7 | ||
|
246dc4f8d1 | ||
|
31efa3e33f | ||
|
bee2a5129b | ||
|
f1e78af80b | ||
|
e0022e549e | ||
|
b46d047411 | ||
|
542c549e4e | ||
|
ebb9a6daa0 | ||
|
f93a94ddf1 | ||
|
597fc95cef | ||
|
df81dda22d | ||
|
5836965a1e | ||
|
58d80b8eb3 | ||
|
a4b4a3f24c | ||
|
ace6019197 | ||
|
88cefeabbf | ||
|
bf2afd8739 | ||
|
0b087ee08c | ||
|
c1746697b9 | ||
|
5179c4dbb5 | ||
|
64973f57cf | ||
|
00a00be692 | ||
|
9e8dade238 | ||
|
9babad6b33 | ||
|
00896ab280 | ||
|
f03cd76f52 | ||
|
0453c1db30 | ||
|
013be139c9 | ||
|
317fc367f8 | ||
|
efc54e73f2 | ||
|
0182ec7251 | ||
|
640ccb7ef8 | ||
|
2e694349c9 | ||
|
9328fa1eb4 | ||
|
221d823f3b | ||
|
8b6eadb9ab | ||
|
22d8380bce | ||
|
0cbf1a83a5 | ||
|
23f717579e | ||
|
4eb8c95c3a | ||
|
0fa04e4a54 | ||
|
f97aa8cbef | ||
|
37b0f29184 | ||
|
2befdbb54f | ||
|
d1a6c84e6b | ||
|
e8229b867a | ||
|
6fb18d5827 | ||
|
a8488a0745 | ||
|
89a7416367 | ||
|
068c6b8555 |
71
.github/workflows/codeql-analysis.yml.disabled
vendored
Normal file
71
.github/workflows/codeql-analysis.yml.disabled
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 3 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['java']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 0
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,7 +7,8 @@ out/
|
||||
.idea/
|
||||
|
||||
# Local dev config
|
||||
/mxids.yaml
|
||||
/application.yaml
|
||||
|
||||
# Local dev storage
|
||||
/mxisd.db
|
||||
/mxids.db
|
||||
|
12
.travis.yml
12
.travis.yml
@@ -1,4 +1,8 @@
|
||||
language: groovy
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
language: java
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
39
Dockerfile
39
Dockerfile
@@ -1,11 +1,36 @@
|
||||
FROM openjdk:8-jre-alpine
|
||||
# Use a specific version of OpenJDK based on Debian ("bullseye" in this case)
|
||||
FROM --platform=$BUILDPLATFORM openjdk:21-jdk-bullseye AS builder
|
||||
|
||||
VOLUME /etc/mxisd
|
||||
VOLUME /var/mxisd
|
||||
# Replace 'apk' commands with 'apt-get' for Debian-based package management.
|
||||
# Install required packages such as 'git' and 'gradle'. Remember to update and clean up properly.
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gradle git && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /mxids
|
||||
COPY . .
|
||||
RUN ./gradlew shadowJar
|
||||
|
||||
# Second stage: Setup the runtime container
|
||||
FROM openjdk:21-jdk-bullseye
|
||||
|
||||
# Again, switch to 'apt-get' for installing 'bash'. Clean up to keep the image size down.
|
||||
RUN apt-get update && \
|
||||
apt-get install -y bash && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
VOLUME /etc/mxids
|
||||
VOLUME /var/mxids
|
||||
EXPOSE 8090
|
||||
|
||||
ADD build/libs/mxisd.jar /mxisd.jar
|
||||
ADD src/docker/start.sh /start.sh
|
||||
|
||||
ENV JAVA_OPTS=""
|
||||
CMD [ "/start.sh" ]
|
||||
ENV CONF_FILE_PATH="/etc/mxids/mxids.yaml"
|
||||
ENV SIGN_KEY_PATH="/var/mxids/sign.key"
|
||||
ENV SQLITE_DATABASE_PATH="/var/mxids/mxids.db"
|
||||
|
||||
# It's usually a good practice to use 'COPY' instead of 'ADD' for local files unless you need the extra capabilities of 'ADD' (like auto-extracting tar files).
|
||||
COPY src/docker/start.sh /start.sh
|
||||
COPY src/script/mxids /app/mxids
|
||||
COPY --from=builder /mxids/build/libs/mxids.jar /app/mxids.jar
|
||||
|
||||
CMD ["/start.sh"]
|
||||
|
16
DockerfileX
Normal file
16
DockerfileX
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM --platform=$BUILDPLATFORM openjdk:11.0.16-jre-slim
|
||||
|
||||
VOLUME /etc/mxids
|
||||
VOLUME /var/mxids
|
||||
EXPOSE 8090
|
||||
|
||||
ENV JAVA_OPTS=""
|
||||
ENV CONF_FILE_PATH="/etc/mxids/mxids.yaml"
|
||||
ENV SIGN_KEY_PATH="/var/mxids/sign.key"
|
||||
ENV SQLITE_DATABASE_PATH="/var/mxids/mxids.db"
|
||||
|
||||
CMD [ "/start.sh" ]
|
||||
|
||||
ADD src/docker/start.sh /start.sh
|
||||
ADD src/script/mxids /app/mxids
|
||||
ADD build/libs/mxids.jar /app/mxids.jar
|
298
README.md
298
README.md
@@ -1,209 +1,119 @@
|
||||
mxisd - Federated Matrix Identity Server Daemon
|
||||
-----
|
||||

|
||||
ma1sd - Federated Matrix Identity Server
|
||||
----------------------------------------
|
||||

|
||||
|
||||
[Overview](#overview) | [Features](#features) | [Lookup process](#lookup-process) | [Packages](#packages) |
|
||||
[From source](#from-source) | [Configuration](#configuration) | [Network Discovery](#network-discovery) |
|
||||
[Integration](#integration) | [Support](#support)
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Use cases](#use-cases)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Support](#support)
|
||||
- [Contribute](#contribute)
|
||||
- [Powered by ma1sd](#powered-by-ma1sd)
|
||||
- [FAQ](#faq)
|
||||
- [Migration from mxisd](#migration-from-mxisd)
|
||||
- [Contact](#contact)
|
||||
|
||||
---
|
||||
|
||||
* This project is a fork (not successor) of the https://github.com/kamax-matrix/mxisd, which has been archived and no longer maintained as a standalone product.
|
||||
Also, ma1sd is supported by the volunteer not developers of the original project.
|
||||
|
||||
---
|
||||
|
||||
# Overview
|
||||
mxisd is a Federated Matrix Identity server aimed to self-hosted Matrix infrastructures.
|
||||
ma1sd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
|
||||
As an enhanced Identity service, it implements the [Identity service API](https://matrix.org/docs/spec/identity_service/r0.2.0.html)
|
||||
and several [extra features](#features) that greatly enhance user experience within Matrix.
|
||||
It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a
|
||||
single coherent product.
|
||||
|
||||
mxisd uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing:
|
||||
- Local identity stores: LDAP, etc.
|
||||
- Federated identity stores: another Identity Server in charge of a specific domain, if applicable
|
||||
- Configured identity stores: another Identity Server specifically configured, if part of some sort of group trust
|
||||
- Root identity store: vector.im/matrix.org central Identity Servers
|
||||
ma1sd is specifically designed to connect to an existing on-premise Identity store (AD/Samba/LDAP, SQL Database,
|
||||
Web services/app, etc.) and ease the integration of a Matrix infrastructure within an existing one.
|
||||
Check [our FAQ entry](docs/faq.md#what-kind-of-setup-is-ma1sd-really-designed-for) to know if ma1sd is a good fit for you.
|
||||
|
||||
mxisd provides an alternative to [sydent](https://github.com/matrix-org/sydent), while still connecting to the vector.im and matrix.org Identity servers,
|
||||
by implementing the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html).
|
||||
The core principle of ma1sd is to map between Matrix IDs and 3PIDs (Third-Party IDentifiers) for the Homeserver and its
|
||||
users. 3PIDs can be anything that uniquely and globally identify a user, like:
|
||||
- Email address
|
||||
- Phone number
|
||||
- Skype/Live ID
|
||||
- Twitter handle
|
||||
- Facebook ID
|
||||
|
||||
mxisd only aims to support workflows that do NOT break federation or basic lookup processes of the Matrix ecosystem.
|
||||
If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**.
|
||||
|
||||
# Features
|
||||
- Single lookup of 3PID (E-mail, phone number, etc.) by the Matrix Client or Homeserver.
|
||||
- Bulk lookups when trying to find possible matches within contacts in Android and iOS clients.
|
||||
- Bind of 3PID by a Matrix user within a Matrix client.
|
||||
- Support of invitation to rooms by e-mail with e-mail notification to invitee.
|
||||
- Authentication support in [synapse](https://github.com/matrix-org/synapse) via the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth).
|
||||
[Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://matrix.org/docs/spec/identity_service/r0.2.0.html#general-principles):
|
||||
- Search for people by 3PID using its own Identity stores
|
||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#association-lookup))
|
||||
- Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.)
|
||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#invitation-storage))
|
||||
- Allow users to add/remove 3PIDs to their settings/profile via 3PID sessions
|
||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#establishing-associations))
|
||||
- Register accounts on your Homeserver with 3PIDs
|
||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#establishing-associations))
|
||||
|
||||
In the pipe:
|
||||
- Support to proxy 3PID bindings in user profile to the central Matrix.org servers
|
||||
As an enhanced Identity service:
|
||||
- [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID,
|
||||
allowing to fetch data from:
|
||||
- Own Identity store(s)
|
||||
- Federated Identity servers, if applicable to the 3PID
|
||||
- Arbitrary Identity servers
|
||||
- Central Matrix Identity servers
|
||||
- [Session Control](docs/threepids/session/session.md): Extensive control of where 3PIDs are transmitted so they are not
|
||||
leaked publicly by users
|
||||
- [Registration control](docs/features/registration.md): Control and restrict user registration based on 3PID patterns or criterias, like a pending invite
|
||||
- [Authentication](docs/features/authentication.md): Use your Identity stores to perform authentication in [synapse](https://github.com/matrix-org/synapse)
|
||||
via the [REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth)
|
||||
- [Directory search](docs/features/directory.md) which allows you to search for users within your organisation,
|
||||
even without prior contact within Matrix using arbitrary search terms
|
||||
- [Auto-fill of user profile](docs/features/authentication.md#profile-auto-fill) (Display name, 3PIDs)
|
||||
- [Bridge Integration](docs/features/bridge-integration.md): Automatically bridge users without a published Matrix ID
|
||||
|
||||
# Lookup Process
|
||||
Default Lookup strategy will use a priority order and a configurable recursive/local type of request.
|
||||
# Use cases
|
||||
- Use your existing Identity stores, do not duplicate your users information
|
||||
- Auto-fill user profiles with relevant information
|
||||
- As an organisation, stay in control of your data so it is not published to other servers by default where they
|
||||
currently **cannot be removed**
|
||||
- Users can directly find each other using whatever attribute is relevant within your Identity store
|
||||
- Federate your Identity server so you can discover others and/or others can discover you
|
||||
|
||||
## E-mail
|
||||
Given the 3PID `john.doe@example.org`, the following will be performed until a mapping is found:
|
||||
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
|
||||
- DNS: lookup another Identity Server using the domain part of an e-mail and:
|
||||
- Look for a SRV record under `_matrix-identity._tcp.example.org`
|
||||
- Lookup using the base domain name `example.org`
|
||||
- Forwarder: Proxy the request to other configurable identity servers.
|
||||
Also, check [our FAQ entry](docs/faq.md#what-kind-of-setup-is-ma1sd-really-designed-for) to know if ma1sd is a good fit for you.
|
||||
|
||||
## Phone number
|
||||
Given the phone number `+123456789`, the following lookup logic will be performed:
|
||||
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
|
||||
- Forwarder: Proxy the request to other configurable identity servers.
|
||||
|
||||
# Packages
|
||||
See [releases]((https://github.com/kamax-io/mxisd/releases)) for native installers of supported systems.
|
||||
If none is available, please use other packages or build from source.
|
||||
|
||||
## Debian
|
||||
### Download
|
||||
See the [releases section](https://github.com/kamax-io/mxisd/releases).
|
||||
|
||||
### Configure and run
|
||||
After installation:
|
||||
1. Copy the sample config file `/etc/mxisd/mxisd-sample.yaml` to `/etc/mxisd/mxisd.yaml`
|
||||
2. [Configure](#configuration)
|
||||
3. Start the service: `sudo systemctl start mxisd`
|
||||
|
||||
### From source
|
||||
Requirements:
|
||||
- fakeroot
|
||||
- dpkg-deb
|
||||
|
||||
Run:
|
||||
```
|
||||
./gradlew buildDeb
|
||||
```
|
||||
You will find the debian package in `build/dist`
|
||||
|
||||
## Docker
|
||||
```
|
||||
docker pull kamax/mxisd
|
||||
```
|
||||
|
||||
For more info, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)
|
||||
### From source
|
||||
[Build mxisd](#build) then build the docker image:
|
||||
```
|
||||
docker build -t your-org/mxisd:$(git describe --tags --always --dirty) .
|
||||
```
|
||||
You can run a container of the given image and test it with the following command (adapt volumes host paths):
|
||||
```
|
||||
docker run -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t your-org/mxisd:$(git describe --tags --always --dirty)
|
||||
```
|
||||
|
||||
# From Source
|
||||
## Requirements
|
||||
- JDK 1.8
|
||||
|
||||
## Build
|
||||
```
|
||||
git clone https://github.com/kamax-io/mxisd.git
|
||||
cd mxisd
|
||||
./gradlew build
|
||||
```
|
||||
then see the [Configuration](#configuration) section.
|
||||
|
||||
## Test build
|
||||
Start the server in foreground to validate the build:
|
||||
```
|
||||
java -jar build/libs/mxisd.jar
|
||||
```
|
||||
|
||||
Ensure the signing key is available:
|
||||
```
|
||||
$ curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0
|
||||
{"public_key":"..."}
|
||||
```
|
||||
|
||||
Test basic recursive lookup (requires Internet connection with access to TCP 443):
|
||||
```
|
||||
$ curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=mxisd-lookup-test@kamax.io'
|
||||
{"address":"mxisd-lookup-test@kamax.io","medium":"email","mxid":"@mxisd-lookup-test:kamax.io",...}
|
||||
```
|
||||
|
||||
|
||||
If you enabled LDAP, you can also validate your config with a similar request after replacing the `address` value with something present within your LDAP
|
||||
```
|
||||
curl "http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org"
|
||||
```
|
||||
|
||||
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
|
||||
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
|
||||
to an ID server.
|
||||
See the [Integration section](https://github.com/kamax-io/mxisd#integration) for more details.
|
||||
|
||||
## Install
|
||||
After [building](#build) the software, run all the following commands as `root` or using `sudo`
|
||||
1. Prepare files and directories:
|
||||
```
|
||||
# Create a dedicated user
|
||||
useradd -r mxisd
|
||||
|
||||
# Create bin directory
|
||||
mkdir /opt/mxisd
|
||||
|
||||
# Create config directory and set ownership
|
||||
mkdir /etc/mxisd
|
||||
chown mxisd /etc/mxisd
|
||||
|
||||
# Create data directory and set ownership
|
||||
mkdir /var/opt/mxisd
|
||||
chown mxisd /var/opt/mxisd
|
||||
|
||||
# Copy <repo root>/build/libs/mxisd.jar to bin directory
|
||||
cp ./build/libs/mxisd.jar /opt/mxisd/
|
||||
chown mxisd /opt/mxisd/mxisd.jar
|
||||
chmod a+x /opt/mxisd/mxisd.jar
|
||||
|
||||
# Create symlink for easy exec
|
||||
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
|
||||
```
|
||||
|
||||
2. Copy the config file created earlier `./application.example.yaml` to `/etc/mxisd/mxisd.yaml`
|
||||
3. [Configure](#configuration)
|
||||
4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
|
||||
5. Enable service for auto-startup
|
||||
```
|
||||
systemctl enable mxisd
|
||||
```
|
||||
6. Start mxisd
|
||||
```
|
||||
systemctl start mxisd
|
||||
```
|
||||
|
||||
# Configuration
|
||||
After following the specific instructions to create a config file from the sample:
|
||||
1. Set the `server.name` value to the domain value used in your Home Server configuration
|
||||
2. Set an absolute location for the signing keys using `key.path`
|
||||
3. Set a location for the default SQLite persistence using `storage.provider.sqlite.database`
|
||||
4. Configure the E-mail invite sender with items starting in `invite.sender.email`
|
||||
|
||||
If you want to use the LDAP backend:
|
||||
1. Enable it with `ldap.enabled`
|
||||
2. Configure connection options using items starting in `ldap.connection`
|
||||
3. You may want to valid default values for `ldap.attribute` items
|
||||
|
||||
|
||||
# Network Discovery
|
||||
To allow other federated Identity Server to reach yours, configure the following DNS SRV entry (adapt to your values):
|
||||
```
|
||||
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
|
||||
```
|
||||
This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation
|
||||
is currently possible.
|
||||
|
||||
The port must be HTTPS capable. Typically, the port `8090` of mxisd should be behind a reverse proxy which does HTTPS.
|
||||
See the [integration section](#integration) for more details.
|
||||
|
||||
# Integration
|
||||
- [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS)
|
||||
- [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration) as Identity server
|
||||
- [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
|
||||
as authentication module
|
||||
# Getting started
|
||||
See the [dedicated document](docs/getting-started.md)
|
||||
|
||||
# Support
|
||||
## Community
|
||||
If you need help, want to report a bug or just say hi, you can reach us on Matrix at
|
||||
[#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) or [directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/).
|
||||
For more high-level discussion about the Identity Server architecture/API, go to
|
||||
[#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
|
||||
## Troubleshooting
|
||||
A basic troubleshooting guide is available [here](docs/troubleshooting.md).
|
||||
|
||||
## Professional
|
||||
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,
|
||||
please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
|
||||
## Community
|
||||
Over Matrix: [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org) ([Preview](https://view.matrix.org/room/!CxwBdgAlaphCARnKTA:ru-matrix.org/))
|
||||
|
||||
## Commercial
|
||||
Sorry, I cannot provide commercial support (at least now). But always try to help you.
|
||||
|
||||
Don't hesitate to ask about project and feel free to create issues at https://github.com/ma1uta/ma1sd
|
||||
|
||||
# Contribute
|
||||
You can contribute as a community member by:
|
||||
- Giving us feedback about your usage of ma1sd, even if it seems unimportant or if all is working well!
|
||||
- Opening issues for any weird behaviour or bug. ma1sd should feel natural, let us know if it does not!
|
||||
- Helping us improve the documentation: tell us what is good or not good (in an issue or in Matrix), or make a PR with
|
||||
changes you feel improve the doc.
|
||||
- Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3.
|
||||
|
||||
# Powered by ma1sd
|
||||
The following projects can use ma1sd under the hood for some or all their features. Check them out!
|
||||
- [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||
- [matrix-register-bot](https://github.com/krombel/matrix-register-bot)
|
||||
|
||||
# FAQ
|
||||
See the [dedicated document](docs/faq.md)
|
||||
|
||||
# Migration from mxisd
|
||||
|
||||
See the [migration guide](docs/migration-from-mxisd.md)
|
||||
|
||||
# Contact
|
||||
Get in touch via:
|
||||
- Matrix: [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org)
|
||||
|
@@ -1,326 +0,0 @@
|
||||
# Sample configuration file explaining all possible options, their default value and if they are required or not.
|
||||
#
|
||||
# Any optional configuration item will be prefixed by # (comment character) with the configuration item following
|
||||
# directly without any whitespace character.
|
||||
# Default values for optional configuration item will also follow such item.
|
||||
#
|
||||
# Any mandatory configuration item will not be prefixed by # and will also contain a value as example that must be
|
||||
# changed. It is advised to re-create a clean config file with only the required configuration item.
|
||||
|
||||
|
||||
#######################
|
||||
# Server config items #
|
||||
#######################
|
||||
# Indicate on which port the Identity Server will listen.
|
||||
#
|
||||
# This is be default an unencrypted port.
|
||||
# HTTPS can be configured using Tomcat configuration properties.
|
||||
#
|
||||
#server.port: 8090
|
||||
|
||||
|
||||
# Realm under which this Identity Server is authoritative.
|
||||
#
|
||||
# This is used to avoid unnecessary connections and endless recursive lookup.
|
||||
# e.g. domain name in e-mails.
|
||||
server.name: 'example.org'
|
||||
|
||||
|
||||
# Public URL to reach this identity server
|
||||
#
|
||||
# This is used with 3PID invites in room and other Homeserver key verification workflow.
|
||||
# If left unconfigured, it will be generated from the server name
|
||||
#
|
||||
#server.publicUrl: 'https://example.org'
|
||||
|
||||
|
||||
#############################
|
||||
# Signing keys config items #
|
||||
#############################
|
||||
# Absolute path for the Identity Server signing key.
|
||||
# During testing, /var/tmp/mxisd.key is a possible value
|
||||
#
|
||||
# For production, use a stable location like:
|
||||
# - /var/opt/mxisd/sign.key
|
||||
# - /var/local/mxisd/sign.key
|
||||
# - /var/lib/mxisd/sign.key
|
||||
key.path: '/path/to/sign.key'
|
||||
|
||||
|
||||
#################################
|
||||
# Recurisve lookup config items #
|
||||
#################################
|
||||
# Configuration items for recursion-type of lookup
|
||||
#
|
||||
# Lookup access are divided into two types:
|
||||
# - Local
|
||||
# - Remote
|
||||
#
|
||||
# This is similar to DNS lookup and recursion and is therefore prone to the same vulnerabilities.
|
||||
# By default, only non-public hosts are allowed to perform recursive lookup.
|
||||
#
|
||||
# This will also prevent very basic endless loops where host A ask host B, which in turn is configured to ask host A,
|
||||
# which would then ask host B again, etc.
|
||||
|
||||
# Enable recursive lookup globally
|
||||
#
|
||||
#lookup.recursive.enabled: true
|
||||
|
||||
|
||||
# Whitelist of CIDR that will trigger a recursive lookup.
|
||||
# The default list includes all private IPv4 address and the IPv6 loopback.
|
||||
#
|
||||
#lookup.recursive.allowedCidr:
|
||||
# - '127.0.0.0/8'
|
||||
# - '10.0.0.0/8'
|
||||
# - '172.16.0.0/12'
|
||||
# - '192.168.0.0/16'
|
||||
# - '::1/128'
|
||||
|
||||
|
||||
# In case no binding is found, query an application server which implements the single lookup end-point
|
||||
# to return bridge virtual user that would allow the user to be contacted directly by the said bridge.
|
||||
#
|
||||
# If a binding is returned, the application server is not expected to sign the message as it is not meant to be
|
||||
# reachable from the outside.
|
||||
# If a signature is provided, it will be discarded/replaced by this IS implementation (to be implemented).
|
||||
#
|
||||
# IMPORTANT: This bypass the regular Invite system of the Homeserver. It will be up to the Application Server
|
||||
# to handle such invite. Also, if the bridged user were to actually join Matrix later, or if a 3PID binding is found
|
||||
# room rights and history would not be transferred, as it would appear as a regular Matrix user to the Homeserver.
|
||||
#
|
||||
# This configuration is only helpful for Application Services that want to overwrite bridging for 3PID that are
|
||||
# handled by the Homeserver. Do not enable unless the Application Server specifically supports it!
|
||||
|
||||
# Enable unknown 3PID bridging globally
|
||||
#
|
||||
#lookup.recursive.bridge.enabled: false
|
||||
|
||||
|
||||
# Enable unknown 3PID bridging for hosts that are allowed to perform recursive lookups.
|
||||
# Leaving this setting to true is highly recommended in a standard setup, unless this Identity Server
|
||||
# is meant to always return a virtual user MXID even for the outside world.
|
||||
#
|
||||
#lookup.recursive.bridge.recursiveOnly: true
|
||||
|
||||
|
||||
# This mechanism can handle the following scenarios:
|
||||
#
|
||||
# - Single Application Server for all 3PID types: only configure the server value, comment out the rest.
|
||||
#
|
||||
# - Specific Application Server for some 3PID types, default server for the rest: configure the server value and
|
||||
# each specific 3PID type.
|
||||
#
|
||||
# - Only specific 3PID types: do not configure the server value or leave it empty/blank, configure each specific
|
||||
# 3PID type.
|
||||
|
||||
# Default application server to use for all 3PID types. Remove config item or leave empty/blank to disable.
|
||||
#
|
||||
#lookup.recursive.bridge.server: ''
|
||||
|
||||
|
||||
# Configure each 3PID type with a specific application server. Remove config item or leave empty/blank to disable.
|
||||
#
|
||||
#lookup.recursive.bridge.mappings.email: 'http://localhost:8091'
|
||||
#lookup.recursive.bridge.mappings.msisdn: ''
|
||||
|
||||
|
||||
#####################
|
||||
# LDAP config items #
|
||||
#####################
|
||||
|
||||
# Global enable/disable switch
|
||||
#
|
||||
#ldap.enabled: false
|
||||
|
||||
#### Connection related config items
|
||||
# If the connection should be secure
|
||||
#
|
||||
#ldap.connection.tls: false
|
||||
|
||||
|
||||
# Host to connect to
|
||||
#
|
||||
#ldap.connection.host: 'localhost'
|
||||
|
||||
|
||||
# Port to connect to
|
||||
#
|
||||
#ldap.connection.port: 389
|
||||
|
||||
|
||||
# Bind DN to use when performing lookups
|
||||
#
|
||||
#ldap.connection.bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
|
||||
|
||||
|
||||
# Bind password to use
|
||||
#
|
||||
#ldap.connection.bindPassword: 'password'
|
||||
|
||||
|
||||
# Base DN used in all queries
|
||||
#
|
||||
#ldap.connection.baseDn: 'CN=Users,DC=example,DC=org'
|
||||
|
||||
|
||||
#### How to map Matrix attributes with LDAP attributes when performing lookup/auth
|
||||
#
|
||||
# How should we resolve the Matrix ID in case of a match using the attribute.
|
||||
#
|
||||
# The following type are supported:
|
||||
# - uid : the attribute only contains the UID part of the Matrix ID. e.g. 'john.doe' in @john.doe:example.org
|
||||
# - mxid : the attribute contains the full Matrix ID - e.g. '@john.doe:example.org'
|
||||
#
|
||||
#ldap.attribute.uid.type: 'uid'
|
||||
|
||||
|
||||
# The attribute containing the binding itself. This value will be used differently depending on the type.
|
||||
#
|
||||
# /!\ This should match the synapse LDAP Authenticator 'uid' configuration /!\
|
||||
#
|
||||
# Typical values:
|
||||
# - For type 'uid': 'userPrincipalName' or 'uid' or 'saMAccountName'
|
||||
# - For type 'mxid', regardless of the directory type, we recommend using 'pager' as it is a standard attribute and
|
||||
# is typically not used.
|
||||
#
|
||||
#ldap.attribute.uid.value: 'userPrincipalName'
|
||||
|
||||
|
||||
# The display name of the user
|
||||
#
|
||||
#ldap.attribute.name: 'displayName'
|
||||
|
||||
|
||||
#### Configuration section relating the authentication of users performed via LDAP.
|
||||
#
|
||||
# This can be done using the REST Auth module for synapse and pointing it to the identity server.
|
||||
# See https://github.com/maxidor/matrix-synapse-rest-auth
|
||||
#
|
||||
# During authentication, What to filter potential users by, typically by using a dedicated group.
|
||||
# If this value is not set, login check will be performed for all entities within the LDAP
|
||||
#
|
||||
# Example: (memberOf=CN=Matrix Users,CN=Users,DC=example,DC=org)
|
||||
#
|
||||
# /!\ Currently NOT supported due to a possible bug in LDAP library /!\
|
||||
#
|
||||
#ldap.auth.filter: ''
|
||||
|
||||
|
||||
#### Configuration section relating to identity lookups
|
||||
#
|
||||
# E-mail query
|
||||
#
|
||||
#ldap.identity.medium.email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))"
|
||||
|
||||
|
||||
# Phone numbers query
|
||||
#
|
||||
# Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN
|
||||
# This format does not include international prefix (+ or 00) and therefore has to be put in the query.
|
||||
# Adapt this to your needs for each attribute.
|
||||
#
|
||||
#ldap.identity.medium.msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
|
||||
|
||||
|
||||
#######################################
|
||||
# Lookup queries forward config items #
|
||||
#######################################
|
||||
# List of forwarders to use to try to match a 3PID.
|
||||
#
|
||||
# Each server will be tried in the given order, going to the next if no binding was found or an error occurred.
|
||||
# These are the current root Identity Servers of the Matrix network.
|
||||
#
|
||||
#forward.servers:
|
||||
# - "https://matrix.org"
|
||||
# - "https://vector.im"
|
||||
|
||||
|
||||
#############################
|
||||
# 3PID invites config items #
|
||||
#############################
|
||||
#
|
||||
#### E-mail invite sender
|
||||
#
|
||||
# SMTP host
|
||||
invite.sender.email.host: "smtp.example.org"
|
||||
|
||||
|
||||
# SMTP port
|
||||
invite.sender.email.port: 587
|
||||
|
||||
|
||||
# TLS mode for the connection.
|
||||
#
|
||||
# Possible values:
|
||||
# 0 Disable TLS entirely
|
||||
# 1 Enable TLS if supported by server
|
||||
# 2 Force TLS and fail if not available
|
||||
#
|
||||
#invite.sender.email.tls: 1
|
||||
|
||||
|
||||
# Login for SMTP
|
||||
invite.sender.email.login: "matrix-identity@example.org"
|
||||
|
||||
|
||||
# Password for the account
|
||||
invite.sender.email.password: "ThePassword"
|
||||
|
||||
|
||||
# The e-mail to send as. If empty, will be the same as login
|
||||
invite.sender.email.email: "matrix-identity@example.org"
|
||||
|
||||
|
||||
# The display name used in the e-mail
|
||||
#
|
||||
#invite.sender.email.name: "mxisd Identity Server"
|
||||
|
||||
|
||||
# The E-mail template to use, using built-in template by default
|
||||
#
|
||||
# The template is expected to be a full e-mail body, including client headers, using MIME and UTF-8 encoding.
|
||||
# The following headers will be set by mxisd directly and should not be present in the template:
|
||||
# - From
|
||||
# - To
|
||||
# - Date
|
||||
# - Message-Id
|
||||
# - X-Mailer
|
||||
#
|
||||
# The following placeholders are available:
|
||||
# - %DOMAIN% Domain name as per server.name config item
|
||||
# - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org
|
||||
# - %FROM_EMAIL% Value of this section's email config item
|
||||
# - %FROM_NAME% Value of this section's name config item
|
||||
# - %SENDER_ID% Matrix ID of the invitation sender
|
||||
# - %SENDER_NAME% Display name of the invitation sender, empty if not available
|
||||
# - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID%
|
||||
# - %INVITE_MEDIUM% Medium of the invite (e.g. email, msisdn)
|
||||
# - %INVITE_ADDRESS% Address used to invite
|
||||
# - %ROOM_ID% ID of the room where the invitation took place
|
||||
# - %ROOM_NAME% Name of the room, empty if not available
|
||||
# - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID%
|
||||
#
|
||||
#invite.sender.email.template: "/absolute/path/to/file"
|
||||
|
||||
|
||||
############################
|
||||
# Persistence config items #
|
||||
############################
|
||||
|
||||
# Configure the storage backend, usually a DB
|
||||
# Possible built-in values:
|
||||
# sqlite SQLite backend, default
|
||||
#
|
||||
#storage.backend: 'sqlite'
|
||||
|
||||
#### Generic SQLite provider config
|
||||
#
|
||||
# Path to the SQLite DB file, required if SQLite backend is chosen
|
||||
#
|
||||
# Examples:
|
||||
# - /var/opt/mxisd/mxisd.db
|
||||
# - /var/local/mxisd/mxisd.db
|
||||
# - /var/lib/mxisd/mxisd.db
|
||||
#
|
||||
storage.provider.sqlite.database: '/path/to/mxisd.db'
|
315
build.gradle
315
build.gradle
@@ -1,10 +1,8 @@
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
* mxids - Matrix Identity Server
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
* 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
|
||||
@@ -20,18 +18,24 @@ import java.util.regex.Pattern
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
import java.util.regex.Pattern
|
||||
|
||||
def confFileName = "application.example.yaml"
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
def confFileName = "mxids.example.yaml"
|
||||
def distDir = "${project.buildDir}/dist"
|
||||
|
||||
def debBinPath = "/usr/lib/mxisd"
|
||||
def debConfPath = "/etc/mxisd"
|
||||
def debDataPath = "/var/lib/mxisd"
|
||||
def debBinPath = "/usr/lib/mxids"
|
||||
def debConfPath = "/etc/mxids"
|
||||
def debDataPath = "/var/lib/mxids"
|
||||
def debSystemdPath = "/etc/systemd/system"
|
||||
|
||||
def debConfFileName = "mxisd-sample.yaml"
|
||||
def debConfFileName = confFileName
|
||||
def debStartScriptFilename = "mxids"
|
||||
|
||||
def debBuildBasePath = "${project.buildDir}/tmp/debian"
|
||||
def debBuildDebianPath = "${debBuildBasePath}/DEBIAN"
|
||||
@@ -40,119 +44,161 @@ def debBuildConfPath = "${debBuildBasePath}${debConfPath}"
|
||||
def debBuildDataPath = "${debBuildBasePath}${debDataPath}"
|
||||
def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}"
|
||||
|
||||
String gitVersion() {
|
||||
def dockerImageName = "cqrenet/mxids"
|
||||
def dockerImageTag = "${dockerImageName}:${mxidsVersion()}"
|
||||
|
||||
group = 'io.kamax'
|
||||
mainClassName = 'io.kamax.mxisd.MxisdStandaloneExec'
|
||||
sourceCompatibility = '11'
|
||||
targetCompatibility = '11'
|
||||
|
||||
String mxidsVersion() {
|
||||
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
|
||||
|
||||
String version = System.getenv('MXIDS_BUILD_VERSION')
|
||||
if (version == null || version.size() == 0) {
|
||||
version = gitVersion()
|
||||
}
|
||||
return versionPattern.matcher(version).matches() ? version.substring(1) : version
|
||||
}
|
||||
|
||||
String gitVersion() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine = [ 'git', 'describe', '--always', '--dirty' ]
|
||||
commandLine = ['git', 'describe', '--tags', '--always', '--dirty']
|
||||
standardOutput = out
|
||||
}
|
||||
def v = out.toString().replace(System.lineSeparator(), '')
|
||||
return versionPattern.matcher(v).matches() ? v.substring(1) : v
|
||||
return out.toString().replace(System.lineSeparator(), '')
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.3.RELEASE'
|
||||
classpath 'com.github.johnrengelman:shadow:8.1.1'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.52.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "https://kamax.io/maven/releases/" }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// We are a groovy project
|
||||
compile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||
// Logging
|
||||
api 'org.slf4j:slf4j-simple:2.0.13'
|
||||
|
||||
// Easy file management
|
||||
compile 'commons-io:commons-io:2.5'
|
||||
api 'commons-io:commons-io:2.20.0'
|
||||
|
||||
// Spring Boot - standalone app
|
||||
compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE'
|
||||
// Config management
|
||||
api 'org.yaml:snakeyaml:1.33'
|
||||
|
||||
// Matrix Java SDK
|
||||
compile 'io.kamax:matrix-java-sdk:0.0.2'
|
||||
|
||||
// ed25519 handling
|
||||
compile 'net.i2p.crypto:eddsa:0.1.0'
|
||||
|
||||
// LDAP connector
|
||||
compile 'org.apache.directory.api:api-all:1.0.0-RC2'
|
||||
|
||||
// DNS lookups
|
||||
compile 'dnsjava:dnsjava:2.1.8'
|
||||
|
||||
// HTTP connections
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.3'
|
||||
|
||||
// JSON
|
||||
compile 'com.google.code.gson:gson:2.8.1'
|
||||
|
||||
// Phone numbers validation
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1'
|
||||
|
||||
// E-mail sending
|
||||
compile 'com.sun.mail:javax.mail:1.5.6'
|
||||
compile 'javax.mail:javax.mail-api:1.5.6'
|
||||
|
||||
// Google Firebase Authentication backend
|
||||
compile 'com.google.firebase:firebase-admin:5.3.0'
|
||||
// Dependencies from old Matrix-java-sdk
|
||||
api 'org.apache.commons:commons-lang3:3.18.0'
|
||||
api 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
api 'commons-codec:commons-codec:1.19.0'
|
||||
|
||||
// ORMLite
|
||||
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
||||
api 'com.j256.ormlite:ormlite-jdbc:6.1'
|
||||
|
||||
// ed25519 handling
|
||||
api 'net.i2p.crypto:eddsa:0.3.0'
|
||||
|
||||
// LDAP connector
|
||||
api 'org.apache.directory.api:api-all:2.1.7'
|
||||
|
||||
// DNS lookups
|
||||
api 'dnsjava:dnsjava:3.6.3'
|
||||
|
||||
// HTTP connections
|
||||
api 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||
|
||||
// Phone numbers validation
|
||||
api 'com.googlecode.libphonenumber:libphonenumber:8.13.36'
|
||||
|
||||
// E-mail sending
|
||||
api 'javax.mail:javax.mail-api:1.6.2'
|
||||
api 'com.sun.mail:javax.mail:1.6.2'
|
||||
|
||||
// Google Firebase Authentication backend
|
||||
api 'com.google.firebase:firebase-admin:9.2.0'
|
||||
|
||||
// Connection Pool
|
||||
api 'com.mchange:c3p0:0.11.2'
|
||||
|
||||
// SQLite
|
||||
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
||||
api 'org.xerial:sqlite-jdbc:3.50.3.0'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
// PostgreSQL
|
||||
api 'org.postgresql:postgresql:42.7.7'
|
||||
|
||||
// MariaDB/MySQL
|
||||
api 'org.mariadb.jdbc:mariadb-java-client:3.5.6'
|
||||
|
||||
// UNIX sockets
|
||||
api 'com.kohlschutter.junixsocket:junixsocket-core:2.9.1'
|
||||
|
||||
// Twilio SDK for SMS
|
||||
api 'com.twilio.sdk:twilio:10.9.2'
|
||||
|
||||
// SendGrid SDK to send emails from GCE
|
||||
api 'com.sendgrid:sendgrid-java:4.10.2'
|
||||
|
||||
// ZT-Exec for exec identity store
|
||||
api 'org.zeroturnaround:zt-exec:1.12'
|
||||
|
||||
// HTTP server
|
||||
api 'io.undertow:undertow-core:2.3.13.Final'
|
||||
|
||||
// Command parser for AS interface
|
||||
api 'commons-cli:commons-cli:1.7.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'com.github.tomakehurst:wiremock:3.0.1'
|
||||
testImplementation 'com.unboundid:unboundid-ldapsdk:7.0.0'
|
||||
testImplementation 'com.icegreen:greenmail:1.6.15'
|
||||
}
|
||||
|
||||
springBoot {
|
||||
executable = true
|
||||
|
||||
embeddedLaunchScriptProperties = [
|
||||
confFolder: "/etc/default"
|
||||
]
|
||||
}
|
||||
|
||||
processResources {
|
||||
doLast {
|
||||
copy {
|
||||
from('build/resources/main/application.yaml') {
|
||||
rename 'application.yaml', 'mxisd.yaml'
|
||||
}
|
||||
into 'build/resources/main'
|
||||
}
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Version': mxidsVersion()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
task buildDeb(dependsOn: build) {
|
||||
shadowJar {
|
||||
archiveBaseName.set(project.name)
|
||||
archiveClassifier.set('') // Set to an empty string if you don't need a classifier.
|
||||
archiveVersion.set('') // Set to an empty string if you don't want the version in the jar name.
|
||||
}
|
||||
|
||||
task debBuild(dependsOn: shadowJar) {
|
||||
doLast {
|
||||
def v = gitVersion()
|
||||
println "Version for package: ${v}"
|
||||
String debVersion = mxidsVersion()
|
||||
println "Version for package: ${debVersion}"
|
||||
mkdir distDir
|
||||
mkdir debBuildBasePath
|
||||
mkdir "${debBuildBasePath}/DEBIAN"
|
||||
mkdir debBuildDebianPath
|
||||
mkdir debBuildBinPath
|
||||
mkdir debBuildConfPath
|
||||
mkdir debBuildDataPath
|
||||
mkdir debBuildSystemdPath
|
||||
|
||||
copy {
|
||||
from "${project.buildDir}/libs/mxisd.jar"
|
||||
from "${project.buildDir}/libs/mxids.jar"
|
||||
into debBuildBinPath
|
||||
}
|
||||
|
||||
ant.chmod(
|
||||
file: "${debBuildBinPath}/mxisd.jar",
|
||||
perm: 'a+x'
|
||||
)
|
||||
copy {
|
||||
from "${project.file("src/script/" + debStartScriptFilename)}"
|
||||
into debBuildBinPath
|
||||
}
|
||||
|
||||
copy {
|
||||
from(project.file(confFileName)) {
|
||||
@@ -161,16 +207,16 @@ task buildDeb(dependsOn: build) {
|
||||
into debBuildConfPath
|
||||
}
|
||||
|
||||
ant.replaceregexp(
|
||||
file: "${debBuildConfPath}/${debConfFileName}",
|
||||
match: "key.path:(.*)",
|
||||
replace: "key.path: '${debDataPath}/signing.key'"
|
||||
ant.replaceregexp( // FIXME adapt to new config format
|
||||
file: "${debBuildConfPath}/${debConfFileName}",
|
||||
match: "key:\\R path:(.*)",
|
||||
replace: "key:\n path: '${debDataPath}/keys'"
|
||||
)
|
||||
|
||||
ant.replaceregexp(
|
||||
file: "${debBuildConfPath}/${debConfFileName}",
|
||||
match: "storage.provider.sqlite.database:(.*)",
|
||||
replace: "storage.provider.sqlite.database: '${debDataPath}/mxisd.db'"
|
||||
ant.replaceregexp( // FIXME adapt to new config format
|
||||
file: "${debBuildConfPath}/${debConfFileName}",
|
||||
match: "storage:\\R provider:\\R sqlite:\\R database:(.*)",
|
||||
replace: "storage:\n provider:\n sqlite:\n database: '${debDataPath}/store.db'"
|
||||
)
|
||||
|
||||
copy {
|
||||
@@ -179,40 +225,105 @@ task buildDeb(dependsOn: build) {
|
||||
}
|
||||
|
||||
ant.replace(
|
||||
file: "${debBuildDebianPath}/control",
|
||||
token: 'Version: 0',
|
||||
value: "Version: ${v}"
|
||||
file: "${debBuildDebianPath}/control",
|
||||
token: 'Version: 0',
|
||||
value: "Version: ${debVersion}"
|
||||
)
|
||||
|
||||
ant.replace(
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
token: '%DEB_DATA_DIR%',
|
||||
value: debDataPath
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
token: '%DEB_DATA_DIR%',
|
||||
value: debDataPath
|
||||
)
|
||||
|
||||
ant.replace(
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
token: '%DEB_CONF_FILE%',
|
||||
value: "${debConfPath}/mxids.yaml"
|
||||
)
|
||||
|
||||
ant.chmod(
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
perm: 'a+x'
|
||||
file: "${debBuildDebianPath}/postinst",
|
||||
perm: '0755'
|
||||
)
|
||||
|
||||
ant.chmod(
|
||||
file: "${debBuildDebianPath}/prerm",
|
||||
perm: 'a+x'
|
||||
file: "${debBuildDebianPath}/prerm",
|
||||
perm: 'a+x'
|
||||
)
|
||||
|
||||
copy {
|
||||
from "${project.file('src/systemd/mxisd.service')}"
|
||||
from "${project.file('src/systemd/mxids.service')}"
|
||||
into debBuildSystemdPath
|
||||
}
|
||||
|
||||
exec {
|
||||
commandLine(
|
||||
'fakeroot',
|
||||
'dpkg-deb',
|
||||
'-b',
|
||||
debBuildBasePath,
|
||||
"${project.buildDir}/dist"
|
||||
'fakeroot',
|
||||
'dpkg-deb',
|
||||
'-b',
|
||||
debBuildBasePath,
|
||||
"${project.buildDir}/dist"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerBuild(type: Exec) {
|
||||
commandLine 'docker', 'build', '-t', dockerImageTag, project.rootDir
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'tag', dockerImageTag, "${dockerImageName}:latest-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerBuildX(type: Exec, dependsOn: shadowJar) {
|
||||
commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', dockerImageTag , project.rootDir
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'buildx', 'build', '--push', '--platform', 'linux/arm64,linux/amd64,linux/arm/v7', '-t', "${dockerImageName}:latest-dev", project.rootDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerPush(type: Exec) {
|
||||
commandLine 'docker', 'push', dockerImageTag
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task dockerPushX(type: Exec) {
|
||||
commandLine 'docker', 'push', dockerImageTag
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-dev"
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-amd64-dev"
|
||||
commandLine 'docker', 'push', "${dockerImageName}:latest-arm64-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('assemble').configure {
|
||||
dependsOn shadowJar
|
||||
}
|
||||
|
||||
tasks.withType(AbstractArchiveTask) {
|
||||
if (it.name == 'distZip' || it.name == 'distTar') {
|
||||
dependsOn shadowJar
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('startScripts').configure {
|
||||
mustRunAfter shadowJar
|
||||
}
|
||||
|
||||
tasks.named('startShadowScripts').configure {
|
||||
dependsOn jar
|
||||
}
|
||||
|
146
docs/MSC2140_MSC2134.md
Normal file
146
docs/MSC2140_MSC2134.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# MSC2140
|
||||
|
||||
## V1 vs V2
|
||||
In the [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) the v2 prefix was introduced.
|
||||
|
||||
Default values:
|
||||
```.yaml
|
||||
matrix:
|
||||
v1: true # deprecated
|
||||
v2: false
|
||||
```
|
||||
|
||||
To disable change value to `false`.
|
||||
|
||||
NOTE: the v1 is deprecated, therefore recommend to use only v2 and disable v1 (default value can be ommited):
|
||||
```.yaml
|
||||
matrix:
|
||||
v1: false
|
||||
```
|
||||
NOTE: Riot Web version 1.5.5 and below checks the v1 for backward compatibility.
|
||||
|
||||
NOTE: v2 disabled by default in order to preserve backward compatibility.
|
||||
|
||||
## Terms
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
Administrator can omit terms configuration. In this case the terms checking will be disabled.
|
||||
|
||||
Example:
|
||||
```.yaml
|
||||
policy:
|
||||
policies:
|
||||
term_name: # term name
|
||||
version: 1.0 # version
|
||||
terms:
|
||||
en: # lang
|
||||
name: term name en # localized name
|
||||
url: https://ma1sd.host.tld/term_en.html # localized url
|
||||
fe: # lang
|
||||
name: term name fr # localized name
|
||||
url: https://ma1sd.host.tld/term_fr.html # localized url
|
||||
regexp:
|
||||
- '/_matrix/identity/v2/account.*'
|
||||
- '/_matrix/identity/v2/hash_details'
|
||||
- '/_matrix/identity/v2/lookup'
|
||||
```
|
||||
Where:
|
||||
|
||||
- `term_name` -- name of the terms.
|
||||
- `version` -- the terms version.
|
||||
- `lang` -- the term language.
|
||||
- `name` -- the name of the term.
|
||||
- `url` -- the url of the term. Might be any url (i.e. from another host) for a html page.
|
||||
- `regexp` -- regexp patterns for API which should be available only after accepting the terms.
|
||||
|
||||
API will be checks for accepted terms only with authorization.
|
||||
There are the next API:
|
||||
- [`GET /_matrix/identity/v2/account`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-account) - Gets information about what user owns the access token used in the request.
|
||||
- [`POST /_matrix/identity/v2/account/logout`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-account-logout) - Logs out the access token, preventing it from being used to authenticate future requests to the server.
|
||||
- [`GET /_matrix/identity/v2/hash_details`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-hash-details) - Gets parameters for hashing identifiers from the server. This can include any of the algorithms defined in this specification.
|
||||
- [`POST /_matrix/identity/v2/lookup`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-lookup) - Looks up the set of Matrix User IDs which have bound the 3PIDs given, if bindings are available. Note that the format of the addresses is defined later in this specification.
|
||||
- [`POST /_matrix/identity/v2/validate/email/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-requesttoken) - Create a session for validating an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`GET /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-requesttoken) - Create a session for validating a phone number.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/3pid/getValidated3pid`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-3pid-getvalidated3pid) - Determines if a given 3pid has been validated by a user.
|
||||
- [`POST /_matrix/identity/v2/3pid/bind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-bind) - Publish an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/3pid/unbind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-unbind) - Remove an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/store-invite`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-store-invite) - Store pending invitations to a user's 3pid.
|
||||
- [`POST /_matrix/identity/v2/sign-ed25519`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519) - Sign invitation details.
|
||||
|
||||
There is only one exception: [`POST /_matrix/identity/v2/terms`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-terms) which uses for accepting the terms and requires the authorization.
|
||||
|
||||
## [Hash lookup](https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md)
|
||||
|
||||
Hashes and the pepper updates together according to the `rotationPolicy`.
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
In case the `none` algorithms ma1sd will be lookup using the v1 bulk API.
|
||||
|
||||
```.yaml
|
||||
hashing:
|
||||
enabled: true # enable or disable the hash lookup MSC2140 (default is false)
|
||||
pepperLength: 20 # length of the pepper value (default is 20)
|
||||
rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
algorithms:
|
||||
- none # the same as v1 bulk lookup
|
||||
- sha256 # hash the 3PID and pepper.
|
||||
delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
```
|
||||
|
||||
When enabled and client requests the `none` algorithms then hash lookups works as v1 bulk lookup.
|
||||
|
||||
Delay specified in the format: `2d 4h 12m 34s` - this means 2 days 4 hours 12 minutes and 34 seconds. Zero units may be omitted. For example:
|
||||
|
||||
- 12s - 12 seconds
|
||||
- 3m - 3 minutes
|
||||
- 5m 6s - 5 minutes and 6 seconds
|
||||
- 6h 3s - 6 hours and 3 seconds
|
||||
|
||||
|
||||
Sha256 algorithm supports only sql, memory and exec 3PID providers.
|
||||
For sql provider (i.e. for the `synapseSql`):
|
||||
```.yaml
|
||||
synapseSql:
|
||||
lookup:
|
||||
query: 'select user_id as mxid, medium, address from user_threepid_id_server' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
For general sql provider:
|
||||
```.yaml
|
||||
sql:
|
||||
lookup:
|
||||
query: 'select user as mxid, field1 as medium, field2 as address from some_table' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
Each query should return the `mxid`, `medium` and `address` fields.
|
||||
|
||||
|
||||
For memory providers:
|
||||
```.yaml
|
||||
memory:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
For exec providers:
|
||||
```.yaml
|
||||
exec:
|
||||
identity:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
For ldap providers:
|
||||
```.yaml
|
||||
ldap:
|
||||
lookup: true
|
||||
```
|
||||
|
||||
NOTE: Federation requests work only with `none` algorithms.
|
||||
|
26
docs/README.md
Normal file
26
docs/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Table of Contents
|
||||
- [Identity Concepts in Matrix](concepts.md)
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Build from sources](build.md) (Optional)
|
||||
- Installation
|
||||
- [Debian package](install/debian.md)
|
||||
- [ArchLinux](install/archlinux.md)
|
||||
- [NixOS](install/nixos.md)
|
||||
- [Docker](install/docker.md)
|
||||
- [From source](install/source.md)
|
||||
- [Architecture overview](architecture.md)
|
||||
- [Configuration](configure.md)
|
||||
- Features
|
||||
- [Authentication](features/authentication.md)
|
||||
- [Directory search](features/directory.md)
|
||||
- [Identity](features/identity.md)
|
||||
- [Federation](features/federation.md)
|
||||
- [Bridge integration](features/bridge-integration.md)
|
||||
- [Identity Stores](stores/README.md)
|
||||
- Notifications
|
||||
- Handlers
|
||||
- [Basic](threepids/notification/basic-handler.md)
|
||||
- [SendGrid](threepids/notification/sendgrid-handler.md)
|
||||
- [Sessions](threepids/session/session.md)
|
||||
- [Views](threepids/session/session-views.md)
|
||||
- [FAQ](faq.md)
|
1
docs/_config.yml
Normal file
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-hacker
|
40
docs/architecture.md
Normal file
40
docs/architecture.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Architecture
|
||||
## Overview
|
||||
### Basic setup with default settings
|
||||
```
|
||||
Client
|
||||
|
|
||||
TCP 443
|
||||
| +---------------------+ +---------------------------+
|
||||
+-> | Reverse proxy | | Homeserver |
|
||||
| | TCP 8008 | |
|
||||
| /_matrix/* -------------------> | - 3PID invite from client |
|
||||
| | | | |
|
||||
| /_matrix/identity/ | | | |
|
||||
+--|------------------+ +---|-----------------------+
|
||||
| |
|
||||
+<---------------------------------<+
|
||||
|
|
||||
| +-------------------+
|
||||
TCP 8090 +-> | mxids |
|
||||
| |
|
||||
| - Profile's 3PIDs |
|
||||
| - 3PID Invites |
|
||||
+-|-----------------+
|
||||
|
|
||||
TCP 443
|
||||
| +------------------------+
|
||||
| | Remote Federated |
|
||||
| | ma1sd servers |
|
||||
| | |
|
||||
+--> - 3PID Invites |
|
||||
+------------------------+
|
||||
```
|
||||
### With Authentication
|
||||
See the [dedicated document](features/authentication.md).
|
||||
|
||||
### With Directory
|
||||
See the [dedicated document](features/directory.md).
|
||||
|
||||
### With Federation
|
||||
See the [dedicated document](features/federation.md).
|
86
docs/build.md
Normal file
86
docs/build.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# From source
|
||||
- [From source](#from-source)
|
||||
- [Binaries](#binaries)
|
||||
- [Requirements](#requirements)
|
||||
- [Build](#build)
|
||||
- [Debian package](#debian-package)
|
||||
- [Docker image](#docker-image)
|
||||
- [Multi-platform builds](#multi-platform-builds)
|
||||
- [Next steps](#next-steps)
|
||||
|
||||
## Binaries
|
||||
### Requirements
|
||||
- JDK 1.8
|
||||
- OpenJDK 11
|
||||
- OpenJDK 14
|
||||
|
||||
### Build
|
||||
```bash
|
||||
git clone https://github.com/ma1uta/ma1sd.git
|
||||
cd ma1sd
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
Create a new configuration file by coping `mxids.example.yaml` to `mxids.yaml` and edit to your needs.
|
||||
For advanced configuration, see the [Configure section](configure.md).
|
||||
|
||||
Start the server in foreground to validate the build and configuration:
|
||||
```bash
|
||||
java -jar build/libs/ma1sd.jar
|
||||
```
|
||||
|
||||
Ensure the signing key is available:
|
||||
```bash
|
||||
$ curl 'http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0'
|
||||
|
||||
{"public_key":"..."}
|
||||
```
|
||||
|
||||
Test basic recursive lookup (requires Internet connection with access to TCP 443):
|
||||
```bash
|
||||
$ curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=ma1sd-federation-test@kamax.io'
|
||||
|
||||
{"address":"ma1sd-federation-test@kamax.io","medium":"email","mxid":"@ma1sd-lookup-test:kamax.io",...}
|
||||
```
|
||||
|
||||
If you enabled LDAP, you can also validate your config with a similar request after replacing the `address` value with
|
||||
something present within your LDAP
|
||||
```bash
|
||||
curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org'
|
||||
```
|
||||
|
||||
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
|
||||
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
|
||||
to an ID server.
|
||||
|
||||
Next step: [Install your compiled binaries](install/source.md)
|
||||
|
||||
## Debian package
|
||||
Requirements:
|
||||
- fakeroot
|
||||
- dpkg-deb
|
||||
|
||||
[Build ma1sd](#build) then:
|
||||
```bash
|
||||
./gradlew debBuild
|
||||
```
|
||||
You will find the debian package in `build/dist`.
|
||||
Then follow the instruction in the [Debian package](install/debian.md) document.
|
||||
|
||||
## Docker image
|
||||
[Build ma1sd](#build) then:
|
||||
```bash
|
||||
./gradlew dockerBuild
|
||||
```
|
||||
Then follow the instructions in the [Docker install](install/docker.md#configure) document.
|
||||
|
||||
### Multi-platform builds
|
||||
|
||||
Provided with experimental docker feature [buildx](https://docs.docker.com/buildx/working-with-buildx/)
|
||||
To build the arm64 and amd64 images run:
|
||||
```bash
|
||||
./gradlew dockerBuildX
|
||||
```
|
||||
|
||||
## Next steps
|
||||
- [Integrate with your infrastructure](getting-started.md#integrate)
|
43
docs/concepts.md
Normal file
43
docs/concepts.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Concepts
|
||||
- [Matrix](#matrix)
|
||||
- [ma1sd](#ma1sd)
|
||||
|
||||
## Matrix
|
||||
The following concepts are part of the Matrix ecosystem and specification.
|
||||
|
||||
### 3PID
|
||||
`3PID` stands for Third-Party Identifier.
|
||||
It is also commonly written:
|
||||
- `3pid`
|
||||
- `tpid`
|
||||
|
||||
A 3PID is a globally unique canonical identifier which is made of:
|
||||
- Medium, which describes what network it belongs to (Email, Phone, Twitter, Discord, etc.)
|
||||
- Address, the actual value people typically use on a daily basis.
|
||||
|
||||
ma1sd core mission is to map those identifiers to Matrix User IDs.
|
||||
|
||||
### Homeserver
|
||||
Where a user **account and data** are stored.
|
||||
|
||||
### Identity server
|
||||
An Identity server:
|
||||
- Does lookup of 3PIDs to User Matrix IDs.
|
||||
- Does validate 3PIDs ownership, typically by sending a code that the user has to enter in an application/on a website.
|
||||
- Does send notifications about room invites where no Matrix User ID could be found for the invitee.
|
||||
|
||||
An Identity server:
|
||||
- **DOES NOT** store user accounts.
|
||||
- **DOES NOT** store user data.
|
||||
- **DOES NOT** allow migration of user account and/or data between homeservers.
|
||||
|
||||
### 3PID session
|
||||
The fact to validate a 3PID (email, phone number, etc.) via the introduction of a token which was sent to the 3PID address.
|
||||
|
||||
## ma1sd
|
||||
The following concepts are specific to ma1sd.
|
||||
|
||||
### Identity store
|
||||
Where your user accounts and 3PID mappings are stored.
|
||||
|
||||
ma1sd itself **DOES NOT STORE** user accounts or 3PID mappings.
|
143
docs/configure.md
Normal file
143
docs/configure.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Configuration
|
||||
- [Concepts](#concepts)
|
||||
- [Syntax](#syntax)
|
||||
- [Matrix](#matrix)
|
||||
- [Server](#server)
|
||||
- [Storage](#storage)
|
||||
- [Identity stores](#identity-stores)
|
||||
- [3PID Validation sessions](#3pid-validation-sessions)
|
||||
- [Notifications](#notifications)
|
||||
|
||||
## Concepts
|
||||
### Syntax
|
||||
The configuration file is [YAML](http://yaml.org/) based:
|
||||
```yaml
|
||||
my:
|
||||
config:
|
||||
item: 'value'
|
||||
|
||||
```
|
||||
|
||||
When referencing keys in all documents, a property-like shorthand will be used. The shorthand for the above example would be `my.config.item`
|
||||
|
||||
## Matrix
|
||||
`matrix.domain`
|
||||
Matrix domain name, same as the Homeserver, used to build appropriate Matrix IDs |
|
||||
|
||||
---
|
||||
|
||||
`matrix.identity.servers`
|
||||
Namespace to create arbitrary list of Identity servers, usable in other parts of the configuration |
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
matrix:
|
||||
identity:
|
||||
servers:
|
||||
myOtherServers:
|
||||
- 'https://other1.example.org'
|
||||
- 'https://other2.example.org'
|
||||
```
|
||||
Create a list under the label `myOtherServers` containing two Identity servers: `https://other1.example.org` and `https://other2.example.org`.
|
||||
|
||||
## Server
|
||||
- `server.name`: Public hostname of ma1sd, if different from the Matrix domain.
|
||||
- `server.port`: HTTP port to listen on (unencrypted)
|
||||
- `server.publicUrl`: Defaults to `https://{server.name}`
|
||||
|
||||
## Unbind (MSC1915)
|
||||
- `session.policy.unbind.enabled`: Enable or disable unbind functionality (MSC1915). (Defaults to true).
|
||||
|
||||
## Hash lookups, Term and others (MSC2140, MSC2134)
|
||||
See the [dedicated document](MSC2140_MSC2134.md) for configuration.
|
||||
|
||||
*Warning*: Unbind check incoming request by two ways:
|
||||
- session validation.
|
||||
- request signature via `X-Matrix` header and uses `server.publicUrl` property to construct the signing json;
|
||||
Commonly the `server.publicUrl` should be the same value as the `trusted_third_party_id_servers` property in the synapse config.
|
||||
|
||||
## Storage
|
||||
### SQLite
|
||||
```yaml
|
||||
storage:
|
||||
backend: sqlite # default
|
||||
provider:
|
||||
sqlite:
|
||||
database: /var/lib/ma1sd/store.db # Absolute location of the SQLite database
|
||||
```
|
||||
|
||||
### Postgresql
|
||||
```yaml
|
||||
storage:
|
||||
backend: postgresql
|
||||
provider:
|
||||
postgresql:
|
||||
database: //localhost:5432/ma1sd
|
||||
username: ma1sd
|
||||
password: secret_password
|
||||
```
|
||||
See [the migration instruction](migration-to-postgresql.md) from sqlite to postgresql
|
||||
|
||||
|
||||
## Logging
|
||||
```yaml
|
||||
logging:
|
||||
root: error # default level for all loggers (apps and thirdparty libraries)
|
||||
app: info # log level only for the ma1sd
|
||||
requests: false # log request and response
|
||||
```
|
||||
|
||||
Possible value: `trace`, `debug`, `info`, `warn`, `error`, `off`.
|
||||
|
||||
Default value for root level: `info`.
|
||||
|
||||
Value for app level can be specified via `MA1SD_LOG_LEVEL` environment variable, configuration or start options.
|
||||
|
||||
Default value for app level: `info`.
|
||||
|
||||
| start option | equivalent configuration |
|
||||
| --- | --- |
|
||||
| | app: info |
|
||||
| -v | app: debug |
|
||||
| -vv | app: trace |
|
||||
|
||||
#### WARNING
|
||||
|
||||
The setting `logging.requests` *MUST NOT* be used in production due it prints full unmasked request and response into the log and can be cause of the data leak.
|
||||
This setting can be used only to testing and debugging errors.
|
||||
|
||||
## Identity stores
|
||||
See the [Identity stores](stores/README.md) for specific configuration
|
||||
|
||||
## 3PID Validation sessions
|
||||
See the dedicated documents:
|
||||
- [Flow](threepids/session/session.md)
|
||||
- [Branding](threepids/session/session-views.md)
|
||||
|
||||
## Notifications
|
||||
- `notification.handler.<3PID medium>`: Handler to use for the given 3PID medium. Repeatable.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
notification:
|
||||
handler:
|
||||
email: 'sendgrid'
|
||||
msisdn: 'raw'
|
||||
```
|
||||
- Emails notifications would use the `sendgrid` handler, which define its own configuration under `notification.handlers.sendgrid`
|
||||
- Phone notification would use the `raw` handler, basic default built-in handler in ma1sd
|
||||
|
||||
### Handlers
|
||||
- `notification.handers.<handler ID>`: Handler-specific configuration for the given handler ID. Repeatable.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
notification:
|
||||
handlers:
|
||||
raw: ...
|
||||
sendgrid: ...
|
||||
```
|
||||
|
||||
Built-in:
|
||||
- [Raw](threepids/notification/basic-handler.md)
|
||||
- [SendGrid](threepids/notification/sendgrid-handler.md)
|
96
docs/faq.md
Normal file
96
docs/faq.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Frequently Asked Questions
|
||||
### This is all very complicated and I'm getting confused with all the words, concepts and diagrams - Help!
|
||||
Matrix is still a very young protocol and there are a whole lot of rough edges.
|
||||
Identity in Matrix is one of the most difficult topic, mainly as it has not received much love in the past years.
|
||||
|
||||
We have tried our best to put together documentation that requires almost no knowledge of Matrix inner workings to get a
|
||||
first basic setup running which relies on you reading the documentation in the right order:
|
||||
- [The Concepts](concepts.md) in few words.
|
||||
- [Getting Started](getting-started.md) step-by-step to a minimal working install.
|
||||
- [Identity stores](stores/README.md) you wish to fetch data from.
|
||||
- [Features](features) you are interested in that will use your Identity store(s) data.
|
||||
|
||||
**IMPORTANT**: Be aware that ma1sd tries to fit within the current protocol and existing products and basic understanding
|
||||
of the Matrix protocol is required for some advanced features.
|
||||
|
||||
If all fails, come over to [the project room](https://matrix.to/#/#ma1sd:ru-matrix.org) and we'll do our best to get you
|
||||
started and answer questions you might have.
|
||||
|
||||
### What kind of setup is ma1sd really designed for?
|
||||
ma1sd is primarily designed for setups that:
|
||||
- [Care for their privacy](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy)
|
||||
- Have their own [domains](https://en.wikipedia.org/wiki/Domain_name)
|
||||
- Use those domains for their email addresses and all other services
|
||||
- Already have an [Identity store](stores/README.md), typically [LDAP-based](stores/ldap.md).
|
||||
|
||||
If you meet all the conditions, then you are the prime use case we designed ma1sd for.
|
||||
|
||||
If you meet some of the conditions, but not all, ma1sd will still be a good fit for you but you won't fully enjoy all its
|
||||
features.
|
||||
|
||||
### Do I need to use ma1sd if I run a Homeserver?
|
||||
No, but it is strongly recommended, even if you don't use any Identity store or integration.
|
||||
|
||||
In its default configuration, ma1sd uses other federated public servers when performing queries.
|
||||
It can also [be configured](features/identity.md#lookups) to use the central matrix.org servers, giving you access to at
|
||||
least the same information as if you were not running it.
|
||||
|
||||
So ma1sd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice
|
||||
simple features on top of it.
|
||||
|
||||
### I'm not sure I understand what an "Identity server" is supposed to be or do...
|
||||
The current Identity service API is more a placeholder, as the Matrix devs did not have time so far to really work on
|
||||
what they want to do with that part of the ecosystem. Therefore, "Identity" is currently a misleading word and concept.
|
||||
Given the scope of the current Identity Service API, it would be best called "Invitation service".
|
||||
|
||||
Because the current scope is so limited and no integration is done with the Homeserver, there was a big lack of features
|
||||
for groups/corporations/organisation. This is where ma1sd comes in.
|
||||
|
||||
ma1sd implements the Identity Service API and also a set of features which are expected by regular users, truly living
|
||||
up to its "Identity server" name.
|
||||
|
||||
### Can I migrate my existing account on another Matrix server with ma1sd?
|
||||
No.
|
||||
|
||||
Accounts cannot currently migrate/move from one server to another.
|
||||
See a [brief explanation document](concepts.md) about Matrix and ma1sd concepts and vocabulary.
|
||||
|
||||
### I already use the synapse LDAP3 auth provider. Why should I care about ma1sd?
|
||||
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) only handles one specific flow: validate credentials at login.
|
||||
|
||||
It does not:
|
||||
- Auto-provision user profiles
|
||||
- Integrate with Identity management
|
||||
- Integrate with Directory searches
|
||||
- Integrate with Profile data
|
||||
|
||||
ma1sd is a replacement and enhancement of it, offering coherent results in all areas, which the LDAP3 auth provider
|
||||
does not.
|
||||
|
||||
### Sydent is the official Identity server implementation of the Matrix team. Why not use that?
|
||||
You can, but [sydent](https://github.com/matrix-org/sydent):
|
||||
- [should not be used and/or self-hosted](https://github.com/matrix-org/sydent/issues/22)
|
||||
- is not meant to be linked to a specific Homeserver / domain
|
||||
- cannot handle federation or proxy lookups, effectively isolating your users from the rest of the network
|
||||
- forces you to duplicate all your identity data, so people can be found by 3PIDs
|
||||
- forces users to enter all their emails and phone numbers manually in their profile
|
||||
|
||||
So really, you should go with ma1sd.
|
||||
|
||||
### Will I loose access to the central Matrix.org/Vector.im Identity data if I use ma1sd?
|
||||
No.
|
||||
|
||||
In its default configuration, ma1sd does not talk to the central Identity server matrix.org to avoid leaking your private
|
||||
data and those of people you might know.
|
||||
|
||||
[You can configure it](features/identity.md#lookups) to talk to the central Identity servers if you wish.
|
||||
|
||||
### So ma1sd is just a big hack! I don't want to use non-official features!
|
||||
ma1sd primary concerns are your privacy and to always be compatible with the Matrix ecosystem and the Identity service API.
|
||||
Whenever the API will be updated and/or enhanced, ma1sd will follow, remaining 100% compatible with the ecosystem.
|
||||
|
||||
### Should I use ma1sd if I don't host my own Homeserver?
|
||||
No.
|
||||
|
||||
It is possible, but it is not supported and the scope of features will be extremely limited.
|
||||
Please consider hosting your own Homeserver and using ma1sd alongside it.
|
251
docs/features/authentication.md
Normal file
251
docs/features/authentication.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# Authentication
|
||||
- [Description](#description)
|
||||
- [Basic](#basic)
|
||||
- [Overview](#overview)
|
||||
- [synapse](#synapse)
|
||||
- [ma1sd](#ma1sd)
|
||||
- [Validate](#validate)
|
||||
- [Next steps](#next-steps)
|
||||
- [Profile auto-fil](#profile-auto-fill)
|
||||
- [Advanced](#advanced)
|
||||
- [Overview](#overview-1)
|
||||
- [Requirements](#requirements)
|
||||
- [Configuration](#configuration)
|
||||
- [Reverse Proxy](#reverse-proxy)
|
||||
- [Apache2](#apache2)
|
||||
- [DNS Overwrite](#dns-overwrite)
|
||||
|
||||
## Description
|
||||
Authentication is an enhanced feature of ma1sd to ensure coherent and centralized identity management.
|
||||
It allows to use Identity stores configured in ma1sd to authenticate users on your Homeserver.
|
||||
|
||||
Authentication is divided into two parts:
|
||||
- [Basic](#basic): authenticate with a regular username.
|
||||
- [Advanced](#advanced): same as basic with extra abilities like authenticate using a 3PID or do username rewrite.
|
||||
|
||||
## Basic
|
||||
Authentication by username is possible by linking synapse and ma1sd together using a specific module for synapse, also
|
||||
known as password provider.
|
||||
|
||||
### Overview
|
||||
An overview of the Basic Authentication process:
|
||||
```
|
||||
Identity stores
|
||||
Client +------+
|
||||
| +-------------------------+ +--> | LDAP |
|
||||
| +---------------+ /_matrix/identity | ma1sd | | +------+
|
||||
+-> | Reverse proxy | >------------------+ | | |
|
||||
+--|------------+ | | | | +--------+
|
||||
| +-----> Check ID stores >------+--> | SQL DB |
|
||||
Login request | | | | +--------+
|
||||
| | | | | |
|
||||
| +--------------------------+ | +-----|-------------------+ +--> ...
|
||||
+-> | Homeserver | | |
|
||||
| | | |
|
||||
| - Validate credentials >----+ |
|
||||
| Using REST auth module | |
|
||||
| | |
|
||||
| - Auto-provision <-------------------<+
|
||||
| user profiles | If valid credentials and supported by Identity store(s)
|
||||
+--------------------------+
|
||||
```
|
||||
Performed on [synapse with REST auth module](https://github.com/ma1uta/matrix-synapse-rest-password-provider/blob/master/README.md)
|
||||
|
||||
### Synapse
|
||||
- Install the [password provider](https://github.com/ma1uta/matrix-synapse-rest-password-provider)
|
||||
- Edit your **synapse** configuration:
|
||||
- As described by the auth module documentation
|
||||
- Set `endpoint` to `http://ma1sdAddress:8090` - Replace `ma1sdAddress` by an IP/host name that provides a direct
|
||||
connection to ma1sd.
|
||||
This **MUST NOT** be a public address, and SHOULD NOT go through a reverse proxy.
|
||||
- Restart synapse
|
||||
|
||||
### ma1sd
|
||||
- Configure and enable at least one [Identity store](../stores/README.md)
|
||||
- Restart ma1sd
|
||||
|
||||
### Validate
|
||||
Login on the Homeserver using credentials present in one of your Identity stores.
|
||||
|
||||
## Next steps
|
||||
### Profile auto-fill
|
||||
Auto-filling user profile depends on its support by your configured Identity stores.
|
||||
See your Identity store [documentation](../stores/README.md) on how to enable the feature.
|
||||
|
||||
|
||||
## Advanced
|
||||
The Authentication feature allows users to:
|
||||
- Rewrite usernames matching a pattern to be mapped to another username via a 3PID.
|
||||
- login to their Homeserver by using their 3PIDs in a configured Identity store.
|
||||
|
||||
This feature also allows to work around the following issues:
|
||||
- Lowercase all usernames for synapse, allowing case-insensitive login
|
||||
- Unable to login on synapse if username is numerical
|
||||
- Any generic transformation of username prior to sending to synapse, bypassing the restriction that password providers
|
||||
cannot change the localpart being authenticated.
|
||||
|
||||
### Overview
|
||||
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/login` as depicted below:
|
||||
```
|
||||
+----------------------------+
|
||||
| Reverse Proxy |
|
||||
| |
|
||||
| | Step 1 +---------------------------+ Step 2
|
||||
| | | |
|
||||
Client+---->| /_matrix/client/r0/login +---------------->| | Look up address +---------+
|
||||
| ^ | | ma1sd - Identity server +----------------->| Backend |
|
||||
| | | | | +---------+
|
||||
| /_matrix/* +--+ +---------------------+ |
|
||||
| | | +---------------+-----------+
|
||||
| | | Step 4 |
|
||||
| | | | Step 3
|
||||
+---------------|------------+ |
|
||||
| | /_matrix/client/r0/login
|
||||
| +--------------+ |
|
||||
| | | |
|
||||
+---------------------->| Homeserver |<----+
|
||||
| |
|
||||
+--------------+
|
||||
|
||||
```
|
||||
|
||||
Steps of user authentication using a 3PID:
|
||||
1. The intercepted login request is directly sent to ma1sd instead of the Homeserver.
|
||||
2. Identity stores are queried for a matching user identity in order to modify the request to use the user name.
|
||||
3. The Homeserver, from which the request was intercepted, is queried using the request at previous step.
|
||||
Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
|
||||
4. The response from the Homeserver is sent back to the client, believing it was the HS which directly answered.
|
||||
|
||||
### Requirements
|
||||
- Compatible [Identity store](../stores/README.md)
|
||||
- [Basic Authentication configured and working](#basic)
|
||||
- Client and Homeserver using the [C2S API r0.4.x](https://matrix.org/docs/spec/client_server/r0.4.0.html) or later
|
||||
- Reverse proxy setup
|
||||
|
||||
### Configuration
|
||||
#### Reverse Proxy
|
||||
##### Apache2
|
||||
The specific configuration to put under the relevant `VirtualHost`:
|
||||
```apache
|
||||
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
|
||||
```
|
||||
`ProxyPreserveHost` or equivalent **must** be enabled to detect to which Homeserver ma1sd should talk to when building results.
|
||||
|
||||
Your VirtualHost should now look similar to:
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName example.org
|
||||
|
||||
...
|
||||
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
|
||||
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
|
||||
ProxyPass /_matrix http://localhost:8008/_matrix
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
##### nginx
|
||||
|
||||
The specific configuration to add under the relevant `server`:
|
||||
|
||||
```nginx
|
||||
location /_matrix/client/r0/login {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
Your `server` section should now look similar to:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name matrix.example.org;
|
||||
|
||||
# ...
|
||||
|
||||
location /_matrix/client/r0/login {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /_matrix/identity {
|
||||
proxy_pass http://localhost:8090/_matrix/identity;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /_matrix {
|
||||
proxy_pass http://localhost:8008/_matrix;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### DNS Overwrite
|
||||
|
||||
Just like you need to configure a reverse proxy to send client requests to ma1sd, you also need to configure ma1sd with
|
||||
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
|
||||
|
||||
To do so, put the following configuration in your ma1sd configuration:
|
||||
```yaml
|
||||
dns:
|
||||
overwrite:
|
||||
homeserver:
|
||||
client:
|
||||
- name: 'example.org'
|
||||
value: 'http://localhost:8008'
|
||||
```
|
||||
`name` must be the hostname of the URL that clients use when connecting to the Homeserver.
|
||||
You can use `${server.name}` to auto-populate the `value` using the `server.name` configuration option and avoid duplicating it.
|
||||
In case the hostname is the same as your Matrix domain and `server.name` is not explicitely set in the config, `server.name` will default to
|
||||
`matrix.domain` and will still probably have the correct value.
|
||||
|
||||
`value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`.
|
||||
|
||||
### Optional features
|
||||
|
||||
The following features are available after you have a working Advanced setup:
|
||||
|
||||
- Username rewrite: Allows you to rewrite the username of a regular login/pass authentication to a 3PID, that then gets resolved using the regular lookup process. Most common use case is to allow login with numerical usernames on synapse, which is not possible out of the box.
|
||||
|
||||
#### Username rewrite
|
||||
In ma1sd config:
|
||||
```yaml
|
||||
auth:
|
||||
rewrite:
|
||||
user:
|
||||
rules:
|
||||
- regex: <your regexp>
|
||||
medium: 'your.custom.medium.type'
|
||||
```
|
||||
`rules` takes a list of rules. Rules have two properties:
|
||||
- `regexp`: The regex pattern to match. This **MUST** match the full string. See [Java regex](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) for syntax.
|
||||
- `medium`: Custom 3PID type that will be used in the 3PID lookup. This can be anything you want and needs to be supported
|
||||
by your Identity store config and/or code.
|
||||
|
||||
Rules are matched in listed order.
|
||||
|
||||
Common regexp patterns:
|
||||
- Numerical usernames: `[0-9]+`
|
||||
|
||||
##### LDAP Example
|
||||
If your users use their numerical employee IDs, which cannot be used with synapse, you can make it work with (relevant config only):
|
||||
```yaml
|
||||
auth:
|
||||
rewrite:
|
||||
user:
|
||||
rules:
|
||||
- regex: '[0-9]+'
|
||||
medium: 'kmx.employee.id'
|
||||
|
||||
ldap:
|
||||
attribute:
|
||||
threepid:
|
||||
kmx.employee.id:
|
||||
- 'ldapAttributeForEmployeeId'
|
||||
```
|
31
docs/features/bridge-integration.md
Normal file
31
docs/features/bridge-integration.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Bridge Integration
|
||||
To help natural bridge integration into the regular usage of a Matrix client, ma1sd provides a way for bridge to reply
|
||||
to 3PID queries if no mapping was found, allowing seamless bridging to a network.
|
||||
|
||||
This is performed by implementing a specific endpoint on the bridge to map a 3PID lookup to a virtual user.
|
||||
|
||||
**NOTE**: This document is incomplete and might be misleading. In doubt, come in our Matrix room.
|
||||
You can also look at our [Email Bridge README](https://github.com/kamax-matrix/matrix-appservice-email#ma1sd) for an example
|
||||
of working configuration.
|
||||
|
||||
## Configuration
|
||||
```yaml
|
||||
lookup:
|
||||
recursive:
|
||||
bridge:
|
||||
enabled: <boolean>
|
||||
recursiveOnly: <boolean>
|
||||
server: <URL to the bridge endpoint for all 3PID medium>
|
||||
mappings:
|
||||
<3PID MEDIUM HERE>: <URL to dedicated bridge for that medium>
|
||||
|
||||
```
|
||||
|
||||
## Integration
|
||||
Implement a simplified version of the [Identity service single lookup endpoint](https://kamax.io/matrix/api/identity_service/unstable.html#get-matrix-identity-api-v1-lookup)
|
||||
with only the following parameters needed:
|
||||
- `address`
|
||||
- `medium`
|
||||
- `mxid`
|
||||
|
||||
Or an empty object if no resolution exists or desired.
|
153
docs/features/directory.md
Normal file
153
docs/features/directory.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# User Directory
|
||||
- [Description](#description)
|
||||
- [Overview](#overview)
|
||||
- [Requirements](#requirements)
|
||||
- [Configuration](#configuration)
|
||||
- [Reverse Proxy](#reverse-proxy)
|
||||
- [Apache2](#apache2)
|
||||
- [nginx](#nginx)
|
||||
- [DNS Overwrite](#dns-overwrite)
|
||||
- [Next steps](#next-steps)
|
||||
|
||||
## Description
|
||||
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
|
||||
or that already share a room with you on the Homeserver.
|
||||
|
||||
Without any integration, synapse:
|
||||
- Only search within the users **already** known to you or in public rooms
|
||||
- Only search on the Display Name and the Matrix ID
|
||||
|
||||
By enabling this feature, you can by default:
|
||||
- Search on Matrix ID, Display name and 3PIDs (Email, phone numbers) of any users already in your configured backend
|
||||
- Search for users which you are not in contact with yet. Super useful for corporations who want to give Matrix access
|
||||
internally, so users can just find themselves **prior** to having any common room(s)
|
||||
- Add extra attributes of your backend to extend the search
|
||||
- Include your homeserver search results to those found by ma1sd
|
||||
|
||||
By integrating ma1sd, you get the default behaviour and a bunch of extras, ensuring your users will always find each other.
|
||||
|
||||
## Overview
|
||||
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/user_directory/search` like so:
|
||||
```
|
||||
+----------------------------------------------+
|
||||
Client --> | Reverse proxy Step 2
|
||||
| Step 1 +-------------------------+
|
||||
| /_matrix/client/r0/user_directory/search ----------> | | Search in +---------+
|
||||
| /\ | ma1sd - Identity server | -----------> | Backend |
|
||||
| /_matrix/* \----------------------------- | | all users +---------+
|
||||
| | Step 4: Send back merged results +-------------------------+
|
||||
+ | |
|
||||
| Step 3
|
||||
| |
|
||||
| +------------+ Search in known users
|
||||
\--> | Homeserver | <----------------------------------------/
|
||||
+------------+ /_matrix/client/r0/user_directory/search
|
||||
```
|
||||
Steps:
|
||||
1. The intercepted request is directly sent to ma1sd instead of the Homeserver.
|
||||
2. Identity stores are queried for any match on the search value sent by the client.
|
||||
3. The Homeserver, from which the request was intercepted, is queried using the same request as the client.
|
||||
Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
|
||||
4. Results from Identity stores and the Homeserver are merged together and sent back to the client, believing it was the HS
|
||||
which directly answered the request.
|
||||
|
||||
## Requirements
|
||||
- Reverse proxy setup, which you should already have in place if you use ma1sd
|
||||
- At least one compatible [Identity store](../stores/README.md) enabled
|
||||
|
||||
## Configuration
|
||||
### Reverse Proxy
|
||||
#### Apache2
|
||||
The specific configuration to put under the relevant `VirtualHost`:
|
||||
```apache
|
||||
ProxyPass /_matrix/client/r0/user_directory/ http://0.0.0.0:8090/_matrix/client/r0/user_directory/
|
||||
```
|
||||
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver ma1sd should talk to when building
|
||||
results.
|
||||
|
||||
Your `VirtualHost` should now look like this:
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName example.org
|
||||
|
||||
...
|
||||
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /_matrix/client/r0/user_directory/ http://localhost:8090/_matrix/client/r0/user_directory/
|
||||
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
|
||||
ProxyPass /_matrix http://localhost:8008/_matrix
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
#### nginx
|
||||
The specific configuration to add under your `server` section is:
|
||||
```nginx
|
||||
location /_matrix/client/r0/user_directory {
|
||||
proxy_pass http://0.0.0.0:8090/_matrix/client/r0/user_directory;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
Your `server` section should now look like this:
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name example.org;
|
||||
|
||||
...
|
||||
|
||||
location /_matrix/client/r0/user_directory {
|
||||
proxy_pass http://localhost:8090/_matrix/client/r0/user_directory;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /_matrix/identity {
|
||||
proxy_pass http://localhost:8090/_matrix/identity;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /_matrix {
|
||||
proxy_pass http://localhost:8008/_matrix;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DNS Overwrite
|
||||
Just like you need to configure a reverse proxy to send client requests to ma1sd, you also need to configure ma1sd with
|
||||
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
|
||||
|
||||
To do so, use the following configuration:
|
||||
```yaml
|
||||
dns:
|
||||
overwrite:
|
||||
homeserver:
|
||||
client:
|
||||
- name: 'example.org'
|
||||
value: 'http://localhost:8008'
|
||||
```
|
||||
- `name` must be the hostname of the URL that clients use when connecting to the Homeserver.
|
||||
- `value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`.
|
||||
|
||||
## Next steps
|
||||
### Homeserver results
|
||||
You can configure if the Homeserver should be queried at all when doing a directory search.
|
||||
To disable Homeserver results, set the following in ma1sd configuration file:
|
||||
```yaml
|
||||
directory:
|
||||
exclude:
|
||||
homeserver: true
|
||||
```
|
||||
|
||||
### 3PID exclusion in search
|
||||
You can configure if the 3PID should also be included when doing a directory search.
|
||||
By default, a search is performed on the 3PIDs. If you would like to not include them:
|
||||
```yaml
|
||||
directory:
|
||||
exclude:
|
||||
threepid: true
|
||||
```
|
122
docs/features/experimental/application-service.md
Normal file
122
docs/features/experimental/application-service.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Application Service
|
||||
**WARNING:** These features are currently highly experimental. They can be removed or modified without notice.
|
||||
All the features requires a Homeserver capable of connecting [Application Services](https://matrix.org/docs/spec/application_service/r0.1.1.html).
|
||||
|
||||
The following capabilities are provided in this feature:
|
||||
- [Admin commands](#admin-commands)
|
||||
- [Email Notification about room invites by Matrix IDs](#email-notification-about-room-invites-by-matrix-ids)
|
||||
- [Auto-reject of expired 3PID invites](#auto-reject-of-expired-3pid-invites)
|
||||
|
||||
## Setup
|
||||
> **NOTE:** Make sure you are familiar with [configuration format and rules](../../configure.md).
|
||||
|
||||
Integration as an Application service is a three steps process:
|
||||
1. Create the baseline ma1sd configuration to allow integration.
|
||||
2. Integrate with the homeserver.
|
||||
3. Configure the specific capabilities, if applicable.
|
||||
|
||||
### Configuration
|
||||
#### Variables
|
||||
Under the `appsvc` namespace:
|
||||
|
||||
| Key | Type | Required | Default | Purpose |
|
||||
|-----------------------|---------|----------|---------|----------------------------------------------------------------|
|
||||
| `enabled` | boolean | No | `false` | Globally enable/disable the feature |
|
||||
| `user.main` | string | No | `ma1sd` | Localpart for the main appservice user |
|
||||
| `endpoint.toHS.url` | string | Yes | *None* | Base URL to the Homeserver |
|
||||
| `endpoint.toHS.token` | string | Yes | *None* | Token to use when sending requests to the Homeserver |
|
||||
| `endpoint.toAS.url` | string | Yes | *None* | Base URL to ma1sd from the Homeserver |
|
||||
| `endpoint.toAS.token` | string | Yes | *None* | Token for the Homeserver to use when sending requests to ma1sd |
|
||||
|
||||
#### Example
|
||||
```yaml
|
||||
appsvc:
|
||||
enabled: true
|
||||
endpoint:
|
||||
toHS:
|
||||
url: 'http://localhost:8008'
|
||||
token: 'ExampleTokenToHS-ChangeMe!'
|
||||
toAS:
|
||||
url: 'http://localhost:8090'
|
||||
token: 'ExampleTokenToAS-ChangeMe!'
|
||||
```
|
||||
### Integration
|
||||
#### Synapse
|
||||
Under the `appsvc.registration.synapse` namespace:
|
||||
|
||||
| Key | Type | Required | Default | Purpose |
|
||||
|--------|--------|----------|--------------------|--------------------------------------------------------------------------|
|
||||
| `id` | string | No | `appservice-ma1sd` | The unique, user-defined ID of this application service. See spec. |
|
||||
| `file` | string | Yes | *None* | If defined, the synapse registration file that should be created/updated |
|
||||
|
||||
##### Example
|
||||
```yaml
|
||||
appsvc:
|
||||
registration:
|
||||
synapse:
|
||||
file: '/etc/matrix-synapse/ma1sd-appservice-registration.yaml'
|
||||
```
|
||||
|
||||
Edit your `homeserver.yaml` and add a new entry to the appservice config file, which should look something like this:
|
||||
```yaml
|
||||
app_service_config_files:
|
||||
- '/etc/matrix-synapse/ma1sd-appservice-registration.yaml'
|
||||
- ...
|
||||
```
|
||||
|
||||
Restart synapse when done to register ma1sd.
|
||||
|
||||
#### Others
|
||||
See your Homeserver documentation on how to integrate.
|
||||
|
||||
## Capabilities
|
||||
### Admin commands
|
||||
#### Setup
|
||||
Min config:
|
||||
```yaml
|
||||
appsvc:
|
||||
feature:
|
||||
admin:
|
||||
allowedRoles:
|
||||
- '+aMatrixCommunity:example.org'
|
||||
- 'SomeLdapGroup'
|
||||
- 'AnyOtherArbitraryRoleFromIdentityStores'
|
||||
```
|
||||
|
||||
#### Use
|
||||
The following steps assume:
|
||||
- `matrix.domain` set to `example.org`
|
||||
- `appsvc.user.main` set to `ma1sd` or not set
|
||||
|
||||
1. Invite `@ma1sd:example.org` to a new direct chat
|
||||
2. Type `!help` to get all available commands
|
||||
|
||||
### Email Notification about room invites by Matrix IDs
|
||||
This feature allows for users found in Identity stores to be instantly notified about Room Invites, regardless if their
|
||||
account was already provisioned on the Homeserver.
|
||||
|
||||
#### Requirements
|
||||
- [Identity store(s)](../../stores/README.md) supporting the Profile feature
|
||||
- At least one email entry in the identity store for each user that could be invited.
|
||||
|
||||
#### Configuration
|
||||
In your ma1sd config file:
|
||||
```yaml
|
||||
synapseSql:
|
||||
enabled: false ## Do not use this line if Synapse is used as an Identity Store
|
||||
type: '<DB TYPE>'
|
||||
connection: '<DB CONNECTION URL>'
|
||||
```
|
||||
|
||||
The `synapseSql` section is optional. It is used to retrieve display names which are not directly accessible in this mode.
|
||||
For details about `type` and `connection`, see the [relevant documentation](../../stores/synapse.md).
|
||||
If you do not configure it, some placeholders will not be available in the notification, like the Room name.
|
||||
|
||||
You can also change the default template of the notification using the `generic.matrixId` template option.
|
||||
See [the Template generator documentation](../../threepids/notification/template-generator.md) for more info.
|
||||
|
||||
#### Test
|
||||
Invite a user which is part of your domain while an appropriate Identity store is used.
|
||||
|
||||
### Auto-reject of expired 3PID invites
|
||||
*TBC*
|
16
docs/features/experimental/profile.md
Normal file
16
docs/features/experimental/profile.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Profile
|
||||
**WARNING**: The following sub-features are considered experimental and not officially supported. Use at your own peril.
|
||||
|
||||
## Public Profile enhancement
|
||||
This feature allows to enhance a public profile query with more info than just Matrix ID and Display name, allowing for
|
||||
custom applications to retrieve custom data not currently provided by synapse, per example.
|
||||
|
||||
**WARNING**: This information can be queried without authentication as per the specification. Do not enable unless in a
|
||||
controlled environment.
|
||||
|
||||
### Configuration
|
||||
#### Reverse proxy
|
||||
##### Apache
|
||||
```apache
|
||||
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1"
|
||||
```
|
51
docs/features/federation.md
Normal file
51
docs/features/federation.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Federation
|
||||
Federation is the process by which domain owners can make compatible 3PIDs mapping auto-discoverable by looking for another
|
||||
Federated Identity server using the DNS domain part of the 3PID.
|
||||
|
||||
Emails are the best candidate for this kind of resolution which are DNS domain based already.
|
||||
On the other hand, Phone numbers cannot be resolved this way.
|
||||
|
||||
For 3PIDs which are not compatible with the DNS system, ma1sd can be configured to talk to fallback Identity servers like
|
||||
the central matrix.org one. See the [Identity feature](identity.md#lookups) for instructions on how to enable it.
|
||||
|
||||
Outbound federation is enabled by default while inbound federation is opt-in and require a specific DNS record.
|
||||
|
||||
## Overview
|
||||
```
|
||||
+-------------------+ +-------------> +----------+
|
||||
| ma1sd | | | Backends |
|
||||
| | | +------> +----------+
|
||||
| | | |
|
||||
| Invites / Lookups | | |
|
||||
Federated | +--------+ | | |
|
||||
Identity ---->| Remote |>-----------+ |
|
||||
Server | +--------+ | |
|
||||
| | |
|
||||
| +--------+ | | +-------------------+
|
||||
Homeserver --->| Local |>------------------+------> | Remote Federated |
|
||||
and clients | +--------+ | | ma1sd servers |
|
||||
+-------------------+ +-------------------+
|
||||
```
|
||||
|
||||
## Inbound
|
||||
If you would like to be reachable for lookups over federation, create the following DNS SRV entry and replace
|
||||
`matrix.example.com` by your Identity server public hostname:
|
||||
```
|
||||
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
|
||||
```
|
||||
|
||||
The port must be HTTPS capable which is what you get in a regular setup with a reverse proxy from 443 to TCP 8090 of ma1sd.
|
||||
|
||||
## Outbound
|
||||
If you would like to disable outbound federation and isolate your identity server from the rest of the Matrix network,
|
||||
use the following ma1sd configuration options:
|
||||
```yaml
|
||||
lookup:
|
||||
recursive:
|
||||
enabled: false
|
||||
invite:
|
||||
resolution:
|
||||
recursive: false
|
||||
```
|
||||
|
||||
There is currently no way to selectively disable federation towards specific servers, but this feature is planned.
|
107
docs/features/identity.md
Normal file
107
docs/features/identity.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Identity
|
||||
Implementation of the [Identity Service API r0.3.0](https://matrix.org/docs/spec/identity_service/r0.3.0.html).
|
||||
|
||||
- [Lookups](#lookups)
|
||||
- [Invitations](#invitations)
|
||||
- [Expiration](#expiration)
|
||||
- [Policies](#policies)
|
||||
- [Resolution](#resolution)
|
||||
- [3PIDs Management](#3pids-management)
|
||||
|
||||
## Lookups
|
||||
If you would like to use the central matrix.org Identity server to ensure maximum discovery at the cost of potentially
|
||||
leaking all your contacts information, add the following to your configuration:
|
||||
```yaml
|
||||
forward:
|
||||
servers:
|
||||
- 'matrix-org'
|
||||
```
|
||||
**NOTE:** You should carefully consider enabling this option, which is discouraged.
|
||||
For more info, see the [relevant issue](https://github.com/kamax-matrix/ma1sd/issues/76).
|
||||
|
||||
## Invitations
|
||||
### Expiration
|
||||
#### Overview
|
||||
Matrix does not provide a mean to remove/cancel pending 3PID invitations with the APIs. The current reference
|
||||
implementations also do not provide any mean to do so. This leads to 3PID invites forever stuck in rooms.
|
||||
|
||||
To provide this functionality, ma1sd uses a workaround: resolve the invite to a dedicated User ID, which can be
|
||||
controlled by ma1sd or a bot/service that will then reject the invite.
|
||||
|
||||
If this dedicated User ID is to be controlled by ma1sd, the [Application Service](experimental/application-service.md)
|
||||
feature must be configured and integrated with your Homeserver, as well as the *Auto-reject 3PID invite capability*.
|
||||
|
||||
#### Configuration
|
||||
```yaml
|
||||
invite:
|
||||
expiration:
|
||||
enabled: true/false
|
||||
after: 5
|
||||
resolveTo: '@john.doe:example.org'
|
||||
```
|
||||
`enabled`
|
||||
- Purpose: Enable or disable the invite expiration feature.
|
||||
- Default: `true`
|
||||
|
||||
`after`
|
||||
- Purpose: Amount of minutes before an invitation expires.
|
||||
- Default: `10080` (7 days)
|
||||
|
||||
`resolveTo`
|
||||
- Purpose: Matrix User ID to resolve the expired invitations to.
|
||||
- Default: Computed from `appsvc.user.inviteExpired` and `matrix.domain`
|
||||
|
||||
### Policies
|
||||
3PID invite policies are the companion feature of [Registration](registration.md). While the Registration feature acts on
|
||||
requirements for the invitee/register, this feature acts on requirement for the one(s) performing 3PID invites, ensuring
|
||||
a coherent system.
|
||||
|
||||
It relies on only allowing people with specific [Roles](profile.md) to perform 3PID invites. This would typically allow
|
||||
a tight-control on a server setup with is "invite-only" or semi-open (relying on trusted people to invite new members).
|
||||
|
||||
It's a middle ground between a closed server, where every user must be created or already exists in an Identity store,
|
||||
and an open server, where anyone can register.
|
||||
|
||||
#### Integration
|
||||
Because Identity Servers do not control 3PID invites as per Matrix spec, ma1sd needs to intercept a set of Homeserver
|
||||
endpoints to apply the policies.
|
||||
|
||||
##### Reverse Proxy
|
||||
###### nginx
|
||||
**IMPORTANT**: Must be placed before your global `/_matrix` entry:
|
||||
```nginx
|
||||
location ~* ^/_matrix/client/r0/rooms/([^/]+)/invite$ {
|
||||
proxy_pass http://127.0.0.1:8090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
The only policy currently available is to restrict 3PID invite to users having a specific (set of) role(s), like so:
|
||||
|
||||
```yaml
|
||||
invite:
|
||||
policy:
|
||||
ifSender:
|
||||
hasRole:
|
||||
- '<THIS_ROLE>'
|
||||
- '<OR_THIS_ROLE>'
|
||||
```
|
||||
|
||||
### Resolution
|
||||
Resolution of 3PID invitations can be customized using the following configuration:
|
||||
|
||||
`invite.resolution.recursive`
|
||||
- Default value: `true`
|
||||
- Description: Control if the pending invite resolution should be done recursively or not.
|
||||
**DANGER ZONE:** This setting has the potential to create "an isolated island", which can have unexpected side effects
|
||||
and break invites in rooms. This will most likely not have the effect you think it does. Only change the value if you
|
||||
understand the consequences.
|
||||
|
||||
`invite.resolution.timer`
|
||||
- Default value: `1`
|
||||
- Description: How often, in minutes, ma1sd should try to resolve pending invites.
|
||||
|
||||
## 3PIDs Management
|
||||
See the [3PID session documents](../threepids/session)
|
10
docs/features/profile.md
Normal file
10
docs/features/profile.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Profile
|
||||
The profile feature does not do anything on its own and acts as a support feature for others, allowing to retrieve
|
||||
information about a user based on its Matrix ID by querying enabled [Identity stores](../stores/README.md).
|
||||
|
||||
Currently supported:
|
||||
- Display name
|
||||
- 3PIDs
|
||||
- Roles/Groups
|
||||
|
||||
Experimental sub-features are also available. See [the dedicated document](experimental/profile.md).
|
111
docs/features/registration.md
Normal file
111
docs/features/registration.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Registration
|
||||
- [Overview](#overview)
|
||||
- [Integration](#integration)
|
||||
- [Reverse Proxy](#reverse-proxy)
|
||||
- [nginx](#nginx)
|
||||
- [Apache2](#apache2)
|
||||
- [Homeserver](#homeserver)
|
||||
- [synapse](#synapse)
|
||||
- [Configuration](#configuration)
|
||||
- [Example](#example)
|
||||
- [Usage](#usage)
|
||||
|
||||
## Overview
|
||||
**NOTE**: This feature is beta: it is considered stable enough for production but is incomplete and may contain bugs.
|
||||
|
||||
Registration is an enhanced feature of ma1sd to control registrations involving 3PIDs on a Homeserver based on policies:
|
||||
- Match pending 3PID invites on the server
|
||||
- Match 3PID pattern, like a specific set of domains for emails
|
||||
- In further releases, use 3PIDs found in Identity stores
|
||||
|
||||
It aims to help open or invite-only registration servers control what is possible to do and ensure only approved people
|
||||
can register on a given server in a implementation-agnostic manner.
|
||||
|
||||
**IMPORTANT:** This feature does not control registration in general. It only acts on endpoints related to 3PIDs during
|
||||
the registration process.
|
||||
As such, it relies on the homeserver to require 3PIDs with the registration flows.
|
||||
|
||||
This feature is not part of the Matrix Identity Server spec.
|
||||
|
||||
## Integration
|
||||
ma1sd needs to be integrated at several levels for this feature to work:
|
||||
- Reverse proxy: intercept the 3PID register endpoints and act on them
|
||||
- Homeserver: require 3PID to be part of the registration data
|
||||
|
||||
Later version(s) of this feature may directly control registration itself to create a coherent experience
|
||||
### Reverse Proxy
|
||||
#### nginx
|
||||
```nginx
|
||||
location ~* ^/_matrix/client/r0/register/[^/]+/requestToken$ {
|
||||
proxy_pass http://localhost:8090;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
```
|
||||
|
||||
#### Apache2
|
||||
> TBC
|
||||
|
||||
### Homeserver
|
||||
#### Synapse
|
||||
```yaml
|
||||
enable_registration: true
|
||||
registrations_require_3pid:
|
||||
- email
|
||||
```
|
||||
|
||||
## Configuration
|
||||
See the [Configuration](../configure.md) introduction doc on how to read the configuration keys.
|
||||
An example of working configuration is available at the end of this section.
|
||||
### Enable/Disable
|
||||
`register.allowed`, taking a boolean, can be used to enable/disable registration if the attempt is not 3PID-based.
|
||||
`false` is the default value to prevent open registration, as you must allow it on the homeserver side.
|
||||
|
||||
### For invites
|
||||
`register.invite`, taking a boolean, controls if registration can be made using a 3PID which matches a pending 3PID invite.
|
||||
`true` is the default value.
|
||||
|
||||
### 3PID-specific
|
||||
At this time, only `email` is supported with 3PID specific configuration with this feature.
|
||||
|
||||
#### Email
|
||||
**Base key**: `register.threepid.email`
|
||||
|
||||
##### Domain whitelist/blacklist
|
||||
If you would like to control which domains are allowed to be used when registering with an email, the following sub-keys
|
||||
are available:
|
||||
- `domain.whitelist`
|
||||
- `domain.blacklist`
|
||||
|
||||
The value format is an hybrid between glob patterns and postfix configuration files with the following syntax:
|
||||
- `*<domain>` will match the domain and any sub-domain(s)
|
||||
- `.<domain>` will only match sub-domain(s)
|
||||
- `<domain>` will only match the exact domain
|
||||
|
||||
The following table illustrates pattern and matching status against example values:
|
||||
|
||||
| Config value | Matches `example.org` | Matches `sub.example.org` |
|
||||
|--------------- |-----------------------|---------------------------|
|
||||
| `*example.org` | Yes | Yes |
|
||||
| `.example.org` | No | Yes |
|
||||
| `example.org` | Yes | No |
|
||||
|
||||
### Example
|
||||
For the following example configuration:
|
||||
```yaml
|
||||
register:
|
||||
policy:
|
||||
threepid:
|
||||
email:
|
||||
domain:
|
||||
whitelist:
|
||||
- '*example.org'
|
||||
- '.example.net'
|
||||
- 'example.com'
|
||||
```
|
||||
- Users can register using 3PIDs of pending invites, being allowed by default.
|
||||
- Users can register using an email from `example.org` and any sub-domain, only sub-domains of `example.net` and `example.com` but not its sub-domains.
|
||||
- Otherwise, user registration will be denied.
|
||||
|
||||
## Usage
|
||||
Nothing special is needed. Register using a regular Matrix client.
|
155
docs/getting-started.md
Normal file
155
docs/getting-started.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Getting started
|
||||
1. [Preparation](#preparation)
|
||||
2. [Install](#install)
|
||||
3. [Configure](#configure)
|
||||
4. [Integrate](#integrate)
|
||||
5. [Validate](#validate)
|
||||
6. [Next steps](#next-steps)
|
||||
|
||||
Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups.
|
||||
This will be a good ground work for further integration with features and your existing Identity stores.
|
||||
|
||||
---
|
||||
|
||||
If you would like a more fully integrated setup out of the box, the [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||
project provides a turn-key full-stack solution, including LDAP and the various ma1sd features enabled and ready.
|
||||
We work closely with the project owner so the latest ma1sd version is always supported.
|
||||
|
||||
If you choose to use it, this Getting Started guide is not applicable - See the project documentation. You may then
|
||||
directly go to the [Next steps](#next-steps).
|
||||
|
||||
## Preparation
|
||||
You will need:
|
||||
- Working Homeserver, ideally with working federation
|
||||
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your ma1sd domain
|
||||
|
||||
If you use synapse:
|
||||
- It requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as ma1sd does
|
||||
not support HTTPS listener at this time.
|
||||
- HTTPS is hardcoded when talking to the Identity server. If your Identity server URL in your client is `https://matrix.example.org/`,
|
||||
then you need to ensure `https://matrix.example.org/_matrix/identity/api/v1/...` will reach ma1sd if called from the synapse host.
|
||||
In doubt, test with `curl` or similar.
|
||||
|
||||
For maximum integration, it is best to have your Homeserver and ma1sd reachable via the same public hostname.
|
||||
|
||||
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/ma1uta/ma1sd/wiki/Gotchas#nating) if you use the same
|
||||
host.
|
||||
|
||||
The following Quick Start guide assumes you will host the Homeserver and ma1sd under the same hostname.
|
||||
If you would like a high-level view of the infrastructure and how each feature is integrated, see the
|
||||
[dedicated document](architecture.md)
|
||||
|
||||
## Install
|
||||
Install via:
|
||||
- [Docker image](install/docker.md)
|
||||
- [Debian package](install/debian.md)
|
||||
- [ArchLinux](install/archlinux.md)
|
||||
- [NixOS](install/nixos.md)
|
||||
- [Sources](build.md)
|
||||
|
||||
See the [Latest release](https://github.com/ma1uta/ma1sd/releases/latest) for links to each.
|
||||
|
||||
## Configure
|
||||
> **NOTE**: Please view the install instruction for your platform, as this step might be optional or already handled for you.
|
||||
|
||||
> **NOTE**: Details about configuration syntax and format are described [here](configure.md)
|
||||
|
||||
If you haven't created a configuration file yet, copy `ma1sd.example.yaml` to where the configuration file is stored given
|
||||
your installation method and edit to your needs.
|
||||
|
||||
The following items must be at least configured:
|
||||
- `matrix.domain` should be set to your Homeserver domain (`server_name` in synapse configuration)
|
||||
- `key.path` will store the signing keys, which must be kept safe! If the file does not exist, keys will be generated for you.
|
||||
- `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.)
|
||||
|
||||
If your HS/ma1sd hostname is not the same as your Matrix domain, configure `server.name`.
|
||||
Complete configuration guide is available [here](configure.md).
|
||||
|
||||
## Integrate
|
||||
For an overview of a typical ma1sd infrastructure, see the [dedicated document](architecture.md)
|
||||
### Reverse proxy
|
||||
#### Apache2
|
||||
In the `VirtualHost` section handling the domain with SSL, add the following and replace `0.0.0.0` by the internal
|
||||
hostname/IP pointing to ma1sd.
|
||||
**This line MUST be present before the one for the homeserver!**
|
||||
```apache
|
||||
ProxyPass /_matrix/identity http://0.0.0.0:8090/_matrix/identity
|
||||
```
|
||||
|
||||
Typical configuration would look like:
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName matrix.example.org
|
||||
|
||||
# ...
|
||||
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
|
||||
ProxyPass /_matrix http://localhost:8008/_matrix
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
#### nginx
|
||||
In the `server` section handling the domain with SSL, add the following and replace `0.0.0.0` with the internal
|
||||
hostname/IP pointing to ma1sd.
|
||||
**This line MUST be present before the one for the homeserver!**
|
||||
```nginx
|
||||
location /_matrix/identity {
|
||||
proxy_pass http://0.0.0.0:8090/_matrix/identity;
|
||||
}
|
||||
```
|
||||
|
||||
Typical configuration would look like:
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name matrix.example.org;
|
||||
|
||||
# ...
|
||||
|
||||
location /_matrix/identity {
|
||||
proxy_pass http://localhost:8090/_matrix/identity;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /_matrix {
|
||||
proxy_pass http://localhost:8008/_matrix;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Synapse (Deprecated with synapse v1.4.0)
|
||||
Add your ma1sd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.
|
||||
In a typical configuration, you would end up with something similar to:
|
||||
```yaml
|
||||
trusted_third_party_id_servers:
|
||||
- matrix.example.org
|
||||
```
|
||||
|
||||
## Validate (Under reconstruction)
|
||||
**NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
|
||||
your installation validated.
|
||||
|
||||
1. Log in using your Matrix client and set `https://matrix.example.org` as your Identity server URL, replacing `matrix.example.org`
|
||||
by the relevant hostname which you configured in your reverse proxy.
|
||||
2. Create a new empty room. All further actions will take place in this room.
|
||||
3. Invite `ma1sd-federation-test@kamax.io`
|
||||
4. The 3PID invite should be turned into a Matrix invite to `@ma1sd-lookup-test:kamax.io`.
|
||||
5. The invited test user will join the room, send a congratulation message and leave.
|
||||
**NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite.
|
||||
|
||||
If it worked, it means you are up and running and can enjoy ma1sd in its basic mode! Congratulations!
|
||||
If it did not work, read the basic [troubleshooting guide](troubleshooting.md), [get in touch](../README.md#support) and
|
||||
we'll do our best to get you started.
|
||||
|
||||
## Next steps
|
||||
Once your ma1sd server is up and running, there are several ways you can enhance and integrate further with your
|
||||
infrastructure:
|
||||
|
||||
- [Enable extra features](features/)
|
||||
- [Use your own Identity stores](stores/README.md)
|
||||
- [Hardening your ma1sd installation](install/security.md)
|
||||
- [Learn about day-to-day operations](operations.md)
|
9
docs/install/archlinux.md
Normal file
9
docs/install/archlinux.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
|
||||
** Outdated due to migrating to fork. **
|
||||
|
||||
---
|
||||
|
||||
# Arch Linux package
|
||||
An Arch Linux package in the AUR repos is maintained by [r3pek](https://matrix.to/#/@r3pek:r3pek.org), a community member.
|
||||
See https://aur.archlinux.org/packages/mxisd/
|
42
docs/install/debian.md
Normal file
42
docs/install/debian.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Debian package
|
||||
## Requirements
|
||||
- Any distribution that supports Java 8
|
||||
|
||||
## Install
|
||||
1. Download the [latest release](https://github.com/ma1uta/ma1sd/releases/latest)
|
||||
2. Run:
|
||||
```bash
|
||||
dpkg -i /path/to/downloaded/ma1sd.deb
|
||||
```
|
||||
## Files
|
||||
| Location | Purpose |
|
||||
|-------------------------------------|----------------------------------------------|
|
||||
| `/etc/ma1sd` | Configuration directory |
|
||||
| `/etc/ma1sd/ma1sd.yaml` | Main configuration file |
|
||||
| `/etc/systemd/system/ma1sd.service` | Systemd configuration file for ma1sd service |
|
||||
| `/usr/lib/ma1sd` | Binaries |
|
||||
| `/var/lib/ma1sd` | Data |
|
||||
| `/var/lib/ma1sd/signing.key` | Default location for ma1sd signing keys |
|
||||
|
||||
## Control
|
||||
Start ma1sd using:
|
||||
```bash
|
||||
sudo systemctl start ma1sd
|
||||
```
|
||||
|
||||
Stop ma1sd using:
|
||||
```bash
|
||||
sudo systemctl stop ma1sd
|
||||
```
|
||||
|
||||
## Troubleshoot
|
||||
All logs are sent to `STDOUT` which are saved in `/var/log/syslog` by default.
|
||||
You can:
|
||||
- grep & tail using `ma1sd`:
|
||||
```
|
||||
tail -n 99 -f /var/log/syslog | grep ma1sd
|
||||
```
|
||||
- use Systemd's journal:
|
||||
```
|
||||
journalctl -f -n 99 -u ma1sd
|
||||
```
|
31
docs/install/docker.md
Normal file
31
docs/install/docker.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
|
||||
** Outdated due to migrating to fork. **
|
||||
|
||||
---
|
||||
|
||||
# Docker
|
||||
## Fetch
|
||||
Pull the latest stable image:
|
||||
```bash
|
||||
docker pull ma1uta/ma1sd
|
||||
```
|
||||
|
||||
## Configure
|
||||
On first run, simply using `MATRIX_DOMAIN` as an environment variable will create a default config for you.
|
||||
You can also provide a configuration file named `ma1sd.yaml` in the volume mapped to `/etc/ma1sd` before starting your
|
||||
container.
|
||||
|
||||
## Run
|
||||
Use the following command after adapting to your needs:
|
||||
- The `MATRIX_DOMAIN` environment variable to yours
|
||||
- The volumes host paths
|
||||
|
||||
```bash
|
||||
docker run --rm -e MATRIX_DOMAIN=example.org -v /data/ma1sd/etc:/etc/ma1sd -v /data/ma1sd/var:/var/ma1sd -p 8090:8090 -t ma1uta/ma1sd
|
||||
```
|
||||
|
||||
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/ma1uta/ma1sd/)
|
||||
|
||||
## Troubleshoot
|
||||
Please read the [Troubleshooting guide](../troubleshooting.md).
|
14
docs/install/nixos.md
Normal file
14
docs/install/nixos.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
|
||||
** Outdated due to migrating to fork. **
|
||||
|
||||
---
|
||||
|
||||
# NixOS package
|
||||
mxisd is available as a NixOS package in the official repos.
|
||||
|
||||
It is maintained by [maximilian](https://matrix.to/#/@maximilian:transformierende-gesellschaft.org), a community member.
|
||||
|
||||
Related resources:
|
||||
- [NixOS](https://nixos.org/)
|
||||
- [The module definition](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/mxisd.nix)
|
30
docs/install/security.md
Normal file
30
docs/install/security.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Security hardening
|
||||
## Overview
|
||||
This document outlines the various operations you may want to perform to increase the security of your installation and
|
||||
avoid leak of credentials/key pairs
|
||||
|
||||
## Configuration
|
||||
Your config file should have the following ownership:
|
||||
- Dedicated user for ma1sd, used to run the software
|
||||
- Dedicated group for ma1sd, used by other applications to access and read configuration files
|
||||
|
||||
Your config file should have the following access:
|
||||
- Read and write for the ma1sd user
|
||||
- Read for the ma1sd group
|
||||
- Nothing for others
|
||||
|
||||
This translates into `640` and be applied with `chmod 640 /path/to/config/file.yaml`.
|
||||
|
||||
## Data
|
||||
The only sensible place is the key store where ma1sd's signing keys are stored. You should therefore limit access to only
|
||||
the ma1sd user, and deny access to anything else.
|
||||
|
||||
Your key store should have the following access:
|
||||
- Read and write for the ma1sd user
|
||||
- Nothing for the ma1sd group
|
||||
- Nothing for others
|
||||
|
||||
The identity store can either be a file or a directory, depending on your version. v1.4 and higher are using a directory,
|
||||
everything before is using a file.
|
||||
- If your version is directory-based, you will want to apply chmod `700` on it.
|
||||
- If your version is file-based, you will want to apply chmod `600` on it.
|
44
docs/install/source.md
Normal file
44
docs/install/source.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Install from sources
|
||||
## Instructions
|
||||
Follow the [build instructions](../build.md) then:
|
||||
|
||||
### Prepare files and directories:
|
||||
```bash
|
||||
# Create a dedicated user
|
||||
useradd -r ma1sd
|
||||
|
||||
# Create config directory
|
||||
mkdir -p /etc/ma1sd
|
||||
|
||||
# Create data directory and set ownership
|
||||
mkdir -p /var/lib/ma1sd
|
||||
chown -R ma1sd /var/lib/ma1sd
|
||||
|
||||
# Create bin directory, copy the jar and launch scriot to bin directory
|
||||
mkdir /usr/lib/ma1sd
|
||||
cp ./build/libs/ma1sd.jar /usr/lib/ma1sd/
|
||||
cp ./src/script/ma1sd /usr/lib/ma1sd
|
||||
chown -R ma1sd /usr/lib/ma1sd
|
||||
chmod a+x /usr/lib/ma1sd/ma1sd
|
||||
|
||||
# Create symlink for easy exec
|
||||
ln -s /usr/lib/ma1sd/ma1sd /usr/bin/ma1sd
|
||||
```
|
||||
|
||||
### Prepare config file
|
||||
Copy the configuration file you've created following the build instructions to `/etc/ma1sd/ma1sd.yaml`
|
||||
|
||||
### Prepare Systemd
|
||||
1. Copy `src/systemd/ma1sd.service` to `/etc/systemd/system/` and edit if needed
|
||||
2. Enable service for auto-startup
|
||||
```bash
|
||||
systemctl enable ma1sd
|
||||
```
|
||||
|
||||
### Run
|
||||
```bash
|
||||
systemctl start ma1sd
|
||||
```
|
||||
|
||||
## Debug
|
||||
ma1sd logs to stdout, which is normally sent to `/var/log/syslog` or `/var/log/messages`.
|
16
docs/migration-from-mxisd.md
Normal file
16
docs/migration-from-mxisd.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Migration from mxisd
|
||||
|
||||
Version 2.0.0 of the ma1sd uses the same format of the database schema and main configuration file as mxisd.
|
||||
|
||||
Migration from mxisd:
|
||||
- install ma1sd via deb package, docker image or zip/tar archive
|
||||
- stop mxisd
|
||||
- copy configuration file (by default /etc/mxisd/mxisd.yaml to /etc/ma1sd/ma1sd.yaml)
|
||||
- copy key store (by default /var/lib/mxisd/keys folder to /var/lib/ma1sd/keys)
|
||||
- copy storage (by default /var/lib/mxisd/store.db to /var/lib/ma1sd/store.db)
|
||||
- change paths in the new config file (ma1sd.yaml). There are options: `key.path` and `storage.provider.sqlite`
|
||||
- start ma1sd
|
||||
|
||||
Due to ma1sd uses the same ports by default as mxisd it isn't necessary to change nginx/apache configuration.
|
||||
|
||||
If you have any troubles with migration don't hesitate to ask questions in [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org) room.
|
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'
|
||||
```
|
||||
|
24
docs/operations.md
Normal file
24
docs/operations.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Operations Guide
|
||||
- [Operations Guide](#operations-guide)
|
||||
- [Overview](#overview)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Backup](#backup)
|
||||
- [Run](#run)
|
||||
- [Restore](#restore)
|
||||
|
||||
## Overview
|
||||
This document gives various information for the day-to-day management and operations of ma1sd.
|
||||
|
||||
## Maintenance
|
||||
ma1sd does not require any maintenance task to run at optimal performance.
|
||||
|
||||
## Backup
|
||||
### Run
|
||||
ma1sd requires all file in its configuration and data directory to be backed up.
|
||||
They are usually located at:
|
||||
- `/etc/ma1sd`
|
||||
- `/var/lib/ma1sd`
|
||||
|
||||
### Restore
|
||||
Reinstall ma1sd, restore the two folders above in the appropriate location (depending on your install method) and you
|
||||
will be good to go. Simply start ma1sd to restore functionality.
|
8
docs/stores/README.md
Normal file
8
docs/stores/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Identity Stores
|
||||
- [Synapse](synapse.md) - Turn your SynapseDB into a self-contained Identity store
|
||||
- [LDAP-based](ldap.md) - Any LDAP-based product like Active Directory, Samba, NetIQ, OpenLDAP
|
||||
- [SQL Databases](sql.md) - Most common databases like MariaDB, MySQL, PostgreSQL, SQLite
|
||||
- [Website / Web service / Web app](rest.md) - Arbitrary REST endpoints
|
||||
- [Executables](exec.md) - Run arbitrary executables with configurable stdin, arguments, environment and stdout
|
||||
- [Wordpress](wordpress.md) - Connect your Wordpress-powered website DB
|
||||
- [Google Firebase](firebase.md) - Use your Firebase users (with experimental SSO support!)
|
506
docs/stores/exec.md
Normal file
506
docs/stores/exec.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# Exec Identity Store
|
||||
- [Features](#features)
|
||||
- [Overview](#overview)
|
||||
- [Configuration](#configuration)
|
||||
- [Global](#global)
|
||||
- [Tokens](#tokens)
|
||||
- [Executable](#executable)
|
||||
- [Input](#input)
|
||||
- [Output](#output)
|
||||
- [Examples](#examples)
|
||||
- [Per-Feature](#per-feature)
|
||||
- [Authentication](#authentication)
|
||||
- [Tokens](#tokens-1)
|
||||
- [Input](#input-1)
|
||||
- [Output](#output-1)
|
||||
- [Directory](#directory)
|
||||
- [Tokens](#tokens-2)
|
||||
- [Input](#input-2)
|
||||
- [Output](#output-2)
|
||||
- [Identity](#identity)
|
||||
- [Single Lookup](#single-lookup)
|
||||
- [Tokens](#tokens-3)
|
||||
- [Input](#input-3)
|
||||
- [Output](#output-3)
|
||||
- [Bulk Lookup](#bulk-lookup)
|
||||
- [Tokens](#tokens-4)
|
||||
- [Input](#input-4)
|
||||
- [Output](#output-4)
|
||||
- [Profile](#profile)
|
||||
- [Tokens](#tokens-5)
|
||||
- [Input](#input-5)
|
||||
- [Output](#output-5)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | Yes |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | Yes |
|
||||
|
||||
This Identity Store lets you run arbitrary commands to handle the various requests in each support feature.
|
||||
It is the most versatile Identity store of ma1sd, allowing you to connect any kind of logic with any executable/script.
|
||||
|
||||
## Overview
|
||||
Each request can be mapping to a fully customizable command configuration.
|
||||
The various parameters can be provided via any combination of:
|
||||
- [Standard Input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin))
|
||||
- [Command-line arguments](https://en.wikipedia.org/wiki/Command-line_interface#Arguments)
|
||||
- [Environment variables](https://en.wikipedia.org/wiki/Environment_variable)
|
||||
|
||||
Each of those supports a set of customizable token which will be replaced prior to running the command, allowing to
|
||||
provide the input values in any number of ways.
|
||||
|
||||
Success and data will be provided via any combination of:
|
||||
- [Exit status](https://en.wikipedia.org/wiki/Exit_status)
|
||||
- [Standard Output](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout))
|
||||
|
||||
Each of those supports a set of configuration item to decide how to process the value and/or in which format.
|
||||
|
||||
All values, inputs and outputs are UTF-8 encoded.
|
||||
|
||||
## Configuration
|
||||
Each feature comes with a set of possible lookup/action which is mapped to a generic configuration item block.
|
||||
We will use the term `Executable` for each lookup/action and `Processor` for each configuration block.
|
||||
|
||||
### Global
|
||||
```yaml
|
||||
exec:
|
||||
enabled: <boolean>
|
||||
```
|
||||
Enable/disable the Identity store at a global/default level. Each feature can still be individually enabled/disabled.
|
||||
|
||||
#### Tokens
|
||||
The following options allow to globally set tokens for value replacement across all features and processors config.
|
||||
Not all features use all tokens, and each feature might also have its own specific tokens. See each feature documentation.
|
||||
|
||||
They can be set within the following scope:
|
||||
|
||||
```yaml
|
||||
exec:
|
||||
token:
|
||||
<token>: '<value>'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The following tokens and default values are available:
|
||||
```yaml
|
||||
localpart: '{localpart}'
|
||||
```
|
||||
Localpart of Matrix User IDs
|
||||
|
||||
```yaml
|
||||
domain: '{domain}'
|
||||
```
|
||||
Domain of Matrix User IDs
|
||||
|
||||
```yaml
|
||||
mxid: '{mxid}'
|
||||
```
|
||||
Full representation of Matrix User IDs
|
||||
|
||||
```yaml
|
||||
medium: '{medium}'
|
||||
```
|
||||
Medium of 3PIDs
|
||||
|
||||
```yaml
|
||||
address: '{address}'
|
||||
```
|
||||
Address of 3PIDs
|
||||
|
||||
```yaml
|
||||
type: '{type}'
|
||||
```
|
||||
Type of query
|
||||
|
||||
```yaml
|
||||
query: '{query}'
|
||||
```
|
||||
Query value
|
||||
|
||||
### Executable
|
||||
*Executable*s have the following options:
|
||||
```yaml
|
||||
command: '/path/to/executableOrScript'
|
||||
|
||||
```
|
||||
Set the executable (relative or absolute) path to be executed. If no command is given, the action will return a "neutral"
|
||||
result if possible or be skipped altogether.
|
||||
|
||||
---
|
||||
|
||||
Command line arguments can be given via a list via both YAML formats:
|
||||
```yaml
|
||||
args:
|
||||
- '-t'
|
||||
- '{token}'
|
||||
- '-v'
|
||||
- 'value'
|
||||
```
|
||||
or
|
||||
```yaml
|
||||
args: ['-t', '{token}', '-v', 'value]
|
||||
```
|
||||
Each argument will be processed for token replacement.
|
||||
|
||||
---
|
||||
|
||||
Environment variables can be given as key/value pairs:
|
||||
```yaml
|
||||
env:
|
||||
ENV_VAR_1: 'value'
|
||||
ENV_VAR_2: '{token}'
|
||||
```
|
||||
Each variable value will be processed for token replacement.
|
||||
|
||||
#### Input
|
||||
Standard input can be configured in the namespaces `input` with:
|
||||
- `type`: The format to use
|
||||
- `template`: The full or partial template with tokens to be used when generating the input
|
||||
|
||||
Not all features and *Executable*s allow for a template to be provided.
|
||||
Templates for listed-based input are not supported at this time.
|
||||
Default templates may be provided per *Executable*.
|
||||
|
||||
The following types are available:
|
||||
- `json`: Use JSON format, shared with the [REST Identity Store](rest.md)
|
||||
- `plain`: Use a custom multi-lines, optionally tab-separated input
|
||||
|
||||
#### Output
|
||||
Standard output can be configured in the namespaces `output` with:
|
||||
- `type`: The format to use
|
||||
- `template`: The full or partial template with tokens to be used when processing the output
|
||||
|
||||
Not all features and *Executable*s allow for a template to be provided.
|
||||
Templates for listed-based output are not supported at this time.
|
||||
Default templates may be provided per *Executable*.
|
||||
|
||||
The following types are available:
|
||||
- `json`: Use JSON format, shared with the [REST Identity Store](rest.md)
|
||||
- `plain`: Use a custom multi-lines, optionally tab-separated output
|
||||
|
||||
### Examples
|
||||
#### Basic
|
||||
```yaml
|
||||
exec:
|
||||
auth:
|
||||
enabled: true
|
||||
command: '/opt/ma1sd-exec/auth.sh'
|
||||
args: ['{localpart}']
|
||||
input:
|
||||
type: 'plain'
|
||||
template: '{password}'
|
||||
env:
|
||||
DOMAIN: '{domain}'
|
||||
```
|
||||
With Authentication enabled, run `/opt/ma1sd-exec/auth.sh` when validating credentials, providing:
|
||||
- A single command-line argument to provide the `localpart` as username
|
||||
- A plain text string with the password token for standard input, which will be replaced by the password to check
|
||||
- A single environment variable `DOMAIN` containing Matrix ID domain, if given
|
||||
|
||||
The command will use the default values for:
|
||||
- Success exit status of `0`
|
||||
- Failure exit status of `1`
|
||||
- Any other exit status considered as error
|
||||
- Standard output will not be processed
|
||||
|
||||
#### Advanced
|
||||
Given the fictional `placeholder` feature:
|
||||
```yaml
|
||||
exec:
|
||||
enabled: true
|
||||
token:
|
||||
mxid: '{matrixId}'
|
||||
auth:
|
||||
token:
|
||||
localpart: '{username}'
|
||||
command: '/path/to/executable'
|
||||
args:
|
||||
- '-u'
|
||||
- '{username}'
|
||||
env:
|
||||
MATRIX_DOMAIN: '{domain}'
|
||||
MATRIX_USER_ID: '{matrixId}'
|
||||
output:
|
||||
type: 'json'
|
||||
exit:
|
||||
success:
|
||||
- 0
|
||||
- 128
|
||||
failure:
|
||||
- 1
|
||||
- 129
|
||||
```
|
||||
With:
|
||||
- The Identity store enabled for all features
|
||||
- A global specific token `{matrixId}` for Matrix User IDs, replacing the default `{mxid}`
|
||||
|
||||
Running `/path/to/executable` providing:
|
||||
- A custom token for localpart, `{username}`, used as a 2nd command-line argument
|
||||
- An extracted Matrix User ID `localpart` provided as the second command line argument, the first one being `-u`
|
||||
- A password, the extracted Matrix `domain` and the full User ID as arbitrary environment variables, respectively
|
||||
`PASSWORD`, `MATRIX_DOMAIN` and `MATRIX_USER_ID`
|
||||
|
||||
After execution:
|
||||
- Process stdout as [JSON](https://en.wikipedia.org/wiki/JSON)
|
||||
- Consider exit status `0` and `128` as success and try to process the stdout for data
|
||||
- Consider exit status `1` and `129` as failure and try to process the stdout for error code and message
|
||||
|
||||
### Per Feature
|
||||
See each dedicated [Feature](#features) section.
|
||||
|
||||
## Authentication
|
||||
The Authentication feature can be enabled/disabled using:
|
||||
```yaml
|
||||
exec:
|
||||
auth:
|
||||
enabled: <true/false>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This feature provides a single *Executable* under the namespace:
|
||||
```yaml
|
||||
exec:
|
||||
auth:
|
||||
...
|
||||
```
|
||||
|
||||
### Tokens
|
||||
The following tokens/default values are specific to this feature:
|
||||
```yaml
|
||||
password: '{password}'
|
||||
```
|
||||
The provided password
|
||||
|
||||
### Input
|
||||
Supported input types and default templates:
|
||||
|
||||
#### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
#### Plain (`plain`)
|
||||
Default template:
|
||||
```
|
||||
{localpart}
|
||||
{domain}
|
||||
{mxid}
|
||||
{password}
|
||||
```
|
||||
|
||||
### Output
|
||||
Supported output types and default templates:
|
||||
|
||||
#### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
#### Plain (`plain`)
|
||||
**NOTE:** This has limited support. Use the JSON type for full support.
|
||||
|
||||
Default template:
|
||||
```
|
||||
[success status, true or 1 are interpreted as success]
|
||||
[display name of the user]
|
||||
```
|
||||
|
||||
## Directory
|
||||
The Directory feature can be enabled/disabled using:
|
||||
```yaml
|
||||
exec:
|
||||
directory:
|
||||
enabled: <true/false>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Two search types configuration namespace are available, using the same input/output formats and templates:
|
||||
|
||||
By name:
|
||||
```yaml
|
||||
exec:
|
||||
directory:
|
||||
search:
|
||||
byName:
|
||||
...
|
||||
```
|
||||
By 3PID:
|
||||
```yaml
|
||||
exec:
|
||||
directory:
|
||||
search:
|
||||
byThreepid:
|
||||
...
|
||||
```
|
||||
|
||||
#### Tokens
|
||||
No specific tokens are available.
|
||||
|
||||
#### Input
|
||||
Supported input types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
##### Plain (`plain`)
|
||||
Default template:
|
||||
```
|
||||
[type of search, following the REST Identity store format]
|
||||
[query string]
|
||||
```
|
||||
|
||||
#### Output
|
||||
Supported output types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
##### Plain (`plain`)
|
||||
**Not supported at this time.** Use the JSON type.
|
||||
|
||||
## Identity
|
||||
The Identity feature can be enabled/disabled using:
|
||||
```yaml
|
||||
exec.identity.enabled: <true/false>
|
||||
```
|
||||
|
||||
### Single lookup
|
||||
Configuration namespace:
|
||||
```yaml
|
||||
exec.identity.lookup.single:
|
||||
...
|
||||
```
|
||||
|
||||
#### Tokens
|
||||
No specific tokens are available.
|
||||
|
||||
#### Input
|
||||
Supported input types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
##### Plain (`plain`)
|
||||
Default template:
|
||||
```
|
||||
{medium}
|
||||
{address}
|
||||
```
|
||||
|
||||
#### Output
|
||||
Supported output types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
##### Plain (`plain`)
|
||||
Default template:
|
||||
```
|
||||
[User ID type, as documented in the REST Identity Store]
|
||||
[User ID value]
|
||||
```
|
||||
|
||||
The User ID type will default to `localpart` if:
|
||||
- Only one line is returned
|
||||
- The first line is empty
|
||||
|
||||
### Bulk lookup
|
||||
Configuration namespace:
|
||||
```yaml
|
||||
exec:
|
||||
identity:
|
||||
lookup:
|
||||
bulk:
|
||||
...
|
||||
```
|
||||
|
||||
#### Tokens
|
||||
No specific tokens are available.
|
||||
|
||||
#### Input
|
||||
Supported input types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
**NOTE:** Custom Templates are not supported.
|
||||
|
||||
Same as the [REST Identity Store](rest.md).
|
||||
|
||||
##### Plain (`plain`)
|
||||
**Not supported at this time.** Use the JSON type.
|
||||
|
||||
#### Output
|
||||
Supported output types and default templates:
|
||||
|
||||
##### JSON (`json`)
|
||||
**NOTE:** Custom Templates are not supported.
|
||||
|
||||
Same as the [REST Identity Store](rest.md).
|
||||
|
||||
##### Plain (`plain`)
|
||||
**Not supported at this time.** Use the JSON type.
|
||||
|
||||
## Profile
|
||||
The Profile feature can be enabled/disabled using:
|
||||
```yaml
|
||||
exec:
|
||||
profile:
|
||||
enabled: <true/false>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The following *Executable*s namespace are available, share the same input/output formats and templates:
|
||||
|
||||
Get Display name:
|
||||
```yaml
|
||||
exec:
|
||||
profile:
|
||||
displayName:
|
||||
...
|
||||
```
|
||||
|
||||
Get 3PIDs:
|
||||
```yaml
|
||||
exec:
|
||||
profile:
|
||||
threePid:
|
||||
...
|
||||
```
|
||||
|
||||
Get Roles:
|
||||
```yaml
|
||||
exec:
|
||||
profile:
|
||||
role:
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
### Tokens
|
||||
No specific tokens are available.
|
||||
|
||||
### Input
|
||||
Supported input types and default templates:
|
||||
|
||||
#### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
#### Plain (`plain`)
|
||||
Default template:
|
||||
```
|
||||
{localpart}
|
||||
{domain}
|
||||
{mxid}
|
||||
```
|
||||
### Output
|
||||
Supported output types and default templates:
|
||||
|
||||
#### JSON (`json`)
|
||||
Same as the [REST Identity Store](rest.md);
|
||||
|
||||
#### Plain (`plain`)
|
||||
**Not supported at this time.** Use the JSON type.
|
59
docs/stores/firebase.md
Normal file
59
docs/stores/firebase.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Google Firebase Identity store
|
||||
https://firebase.google.com/
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | Yes |
|
||||
| [Directory](../features/directory.md) | No |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | No |
|
||||
|
||||
## Requirements
|
||||
This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following
|
||||
information:
|
||||
- Firebase User ID as Matrix username
|
||||
- Firebase token as Matrix password
|
||||
|
||||
If your client is Riot, you will need a custom version.
|
||||
|
||||
## Configuration
|
||||
```yaml
|
||||
firebase:
|
||||
enabled: <boolean>
|
||||
```
|
||||
Enable/disable this identity store.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
firebase:
|
||||
enabled: <boolean>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
firebase:
|
||||
credentials: <string>
|
||||
```
|
||||
Path to the credentials file provided by Google Firebase to use with an external app.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
firebase:
|
||||
credentials: '/path/to/firebase/credentials.json'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
firebase:
|
||||
database: <string>
|
||||
```
|
||||
URL to your Firebase database.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
firebase:
|
||||
database: 'https://my-project.firebaseio.com/'
|
||||
```
|
141
docs/stores/ldap.md
Normal file
141
docs/stores/ldap.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# LDAP Identity store
|
||||
## Supported products:
|
||||
- Samba
|
||||
- Active Directory
|
||||
- OpenLDAP
|
||||
- NetIQ eDirectory
|
||||
|
||||
For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`.
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | Yes |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | Yes |
|
||||
|
||||
## Getting started
|
||||
### Base
|
||||
To use your LDAP backend, add the bare minimum configuration in ma1sd config file:
|
||||
```yaml
|
||||
ldap:
|
||||
enabled: true
|
||||
connection:
|
||||
host: 'ldapHostnameOrIp'
|
||||
port: 389
|
||||
bindDn: 'CN=My User,OU=Users,DC=example,DC=org'
|
||||
bindPassword: 'TheUserPassword'
|
||||
baseDNs:
|
||||
- 'OU=Users,DC=example,DC=org'
|
||||
```
|
||||
These are standard LDAP connection configuration. ma1sd will try to connect on port default port 389 without encryption.
|
||||
|
||||
If you would like to use several Base DNs, simply add more entries under `baseDNs`.
|
||||
|
||||
### TLS/SSL connection
|
||||
If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported):
|
||||
```yaml
|
||||
ldap:
|
||||
connection:
|
||||
tls: true
|
||||
port: 12345
|
||||
```
|
||||
|
||||
### Filter results
|
||||
You can also set a default global filter on any LDAP queries:
|
||||
```yaml
|
||||
ldap:
|
||||
filter: '(memberOf=CN=My Matrix Users,OU=Groups,DC=example,DC=org)'
|
||||
```
|
||||
This example would only return users part of the group called `My Matrix Users`.
|
||||
This can be overwritten or append in each specific flow describe below.
|
||||
|
||||
For supported syntax, see the [LDAP library documentation](http://directory.apache.org/api/user-guide/2.3-searching.html#filter).
|
||||
|
||||
### Attribute mapping
|
||||
LDAP features are based on mapping LDAP attributes to Matrix concepts, like a Matrix ID, its localpart, the user display
|
||||
name, their email(s) and/or phone number(s).
|
||||
|
||||
Default attributes are well suited for Active Directory/Samba. In case you are using a native LDAP backend, you will
|
||||
most certainly configure those mappings.
|
||||
|
||||
#### User ID
|
||||
`ldap.attribute.uid.type`: How to process the User ID (UID) attribute:
|
||||
- `uid` will consider the value as the [Localpart](https://matrix.org/docs/spec/intro.html#user-identifiers)
|
||||
- `mxid` will consider the value as a complete [Matrix ID](https://matrix.org/docs/spec/intro.html#user-identifiers)
|
||||
|
||||
`ldap.attribute.uid.value`: Attribute to use to set the User ID value.
|
||||
|
||||
The following example would set the `sAMAccountName` attribute as a Matrix User ID localpart:
|
||||
```yaml
|
||||
ldap:
|
||||
attribute:
|
||||
uid:
|
||||
type: 'uid'
|
||||
value: 'sAMAccountName'
|
||||
```
|
||||
|
||||
#### Display name
|
||||
Use `ldap.attribute.name`.
|
||||
|
||||
The following example would set the display name to the value of the `cn` attribute:
|
||||
```yaml
|
||||
ldap:
|
||||
attribute:
|
||||
name: 'cn'
|
||||
```
|
||||
|
||||
#### 3PIDs
|
||||
You can also change the attribute lists for 3PID, like email or phone numbers.
|
||||
|
||||
The following example would overwrite the [default list of attributes](../../src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java#L64)
|
||||
for emails and phone number:
|
||||
```yaml
|
||||
ldap:
|
||||
attribute:
|
||||
threepid:
|
||||
email:
|
||||
- 'mail'
|
||||
- 'otherMailAttribute'
|
||||
msisdn:
|
||||
- 'phone'
|
||||
- 'otherPhoneAttribute'
|
||||
```
|
||||
|
||||
## Features
|
||||
### Identity
|
||||
Identity features (related to 3PID invites or searches) are enabled and configured using default values and no specific
|
||||
configuration item is needed to get started.
|
||||
|
||||
#### Configuration
|
||||
- `ldap.identity.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set.
|
||||
- `ldap.identity.medium`: Namespace to overwrite generated queries from the list of attributes for each 3PID medium.
|
||||
|
||||
### Authentication
|
||||
After you have configured and enabled the [feature itself](../features/authentication.md), no further configuration is
|
||||
needed with this identity store to make it work.
|
||||
|
||||
Profile auto-fill is enabled by default. It will use the `ldap.attribute.name` and `ldap.attribute.threepid` configuration
|
||||
options to get a lit of attributes to be used to build the user profile to pass on to synapse during authentication.
|
||||
|
||||
#### Configuration
|
||||
- `ldap.auth.filter`: Specific user filter applied during username search. Global filter is used if blank/not set.
|
||||
|
||||
### Directory
|
||||
After you have configured and enabled the [feature itself](../features/directory.md), no further configuration is
|
||||
needed with this identity store to make it work.
|
||||
|
||||
#### Configuration
|
||||
To set a specific filter applied during directory search, use `ldap.directory.filter`
|
||||
|
||||
If you would like to use extra attributes in search that are not 3PIDs, like nicknames, group names, employee number:
|
||||
```yaml
|
||||
ldap:
|
||||
directory:
|
||||
attribute:
|
||||
other:
|
||||
- 'myNicknameAttribute'
|
||||
- 'memberOf'
|
||||
- 'employeeNumberAttribute'
|
||||
```
|
277
docs/stores/rest.md
Normal file
277
docs/stores/rest.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# REST Identity store
|
||||
The REST backend allows you to query identity data in existing webapps, like:
|
||||
- Forums (phpBB, Discourse, etc.)
|
||||
- Custom Identity stores (Keycloak, ...)
|
||||
- CRMs (Wordpress, ...)
|
||||
- Self-hosted clouds (Nextcloud, ownCloud, ...)
|
||||
|
||||
To integrate this backend with your webapp, you will need to implement the REST endpoints described below.
|
||||
|
||||
## Features
|
||||
| Name | Supported? |
|
||||
|-------------------------------------------------|------------|
|
||||
| [Authentication](../features/authentication.md) | Yes |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | Yes |
|
||||
|
||||
## Configuration
|
||||
| Key | Default | Description |
|
||||
|--------------------------------------|------------------------------------------------|------------------------------------------------------|
|
||||
| `rest.enabled` | `false` | Globally enable/disable the REST backend |
|
||||
| `rest.host` | *None* | Default base URL to use for the different endpoints. |
|
||||
| `rest.endpoints.auth` | `/_ma1sd/backend/api/v1/auth/login` | Validate credentials and get user profile |
|
||||
| `rest.endpoints.directory` | `/_ma1sd/backend/api/v1/directory/user/search` | Search for users by arbitrary input |
|
||||
| `rest.endpoints.identity.single` | `/_ma1sd/backend/api/v1/identity/single` | Endpoint to query a single 3PID |
|
||||
| `rest.endpoints.identity.bulk` | `/_ma1sd/backend/api/v1/identity/bulk` | Endpoint to query a list of 3PID |
|
||||
| `rest.endpoints.profile.displayName` | `/_ma1sd/backend/api/v1/profile/displayName` | Query the display name for a Matrix ID
|
||||
| `rest.endpoints.profile.threepids` | `/_ma1sd/backend/api/v1/profile/threepids` | Query the 3PIDs for a Matrix ID
|
||||
| `rest.endpoints.profile.roles` | `/_ma1sd/backend/api/v1/profile/roles` | Query the Roles for a Matrix ID
|
||||
|
||||
Endpoint values can handle two formats:
|
||||
- URL Path starting with `/` that gets happened to the `rest.host`
|
||||
- Full URL, if you want each endpoint to go to a specific server/protocol/port
|
||||
|
||||
If an endpoint value is configured as an empty string, it will disable that specific feature, essentially bypassing the
|
||||
Identity store for that specific query.
|
||||
|
||||
`rest.host` is mandatory if at least one endpoint is not a full URL.
|
||||
|
||||
## Endpoints
|
||||
### Authentication
|
||||
- Method: `POST`
|
||||
- Content-Type: `application/json` (JSON)
|
||||
- Encoding: `UTF8`
|
||||
|
||||
#### Request Body
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"mxid": "@john.doe:example.org",
|
||||
"localpart": "john.doe",
|
||||
"domain": "example.org",
|
||||
"password": "passwordOfTheUser"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response Body
|
||||
If the authentication fails:
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"success": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the authentication succeed:
|
||||
- `auth.id` supported values: `localpart`, `mxid`
|
||||
- `auth.profile` and any sub-member are all optional
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"success": true,
|
||||
"id": {
|
||||
"type": "localpart",
|
||||
"value": "john"
|
||||
},
|
||||
"profile": {
|
||||
"display_name": "John Doe",
|
||||
"three_pids": [
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org"
|
||||
},
|
||||
{
|
||||
"medium": "msisdn",
|
||||
"address": "123456789"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Directory
|
||||
- Method: `POST`
|
||||
- Content-Type: `application/json` (JSON)
|
||||
- Encoding: `UTF8`
|
||||
|
||||
#### Request Body
|
||||
```json
|
||||
{
|
||||
"by": "<search type>",
|
||||
"search_term": "doe"
|
||||
}
|
||||
```
|
||||
`by` can be:
|
||||
- `name`
|
||||
- `threepid`
|
||||
|
||||
#### Response Body:
|
||||
If users found:
|
||||
```json
|
||||
{
|
||||
"limited": false,
|
||||
"results": [
|
||||
{
|
||||
"avatar_url": "http://domain.tld/path/to/avatar.png",
|
||||
"display_name": "John Doe",
|
||||
"user_id": "UserIdLocalpart"
|
||||
},
|
||||
{
|
||||
"...": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If no user found:
|
||||
```json
|
||||
{
|
||||
"limited": false,
|
||||
"results": []
|
||||
}
|
||||
```
|
||||
|
||||
### Identity
|
||||
#### Single 3PID lookup
|
||||
- Method: `POST`
|
||||
- Content-Type: `application/json` (JSON)
|
||||
- Encoding: `UTF8`
|
||||
|
||||
##### Request Body
|
||||
```json
|
||||
{
|
||||
"lookup": {
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Response Body
|
||||
If a match was found:
|
||||
- `lookup.id.type` supported values: `localpart`, `mxid`
|
||||
```json
|
||||
{
|
||||
"lookup": {
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org",
|
||||
"id": {
|
||||
"type": "mxid",
|
||||
"value": "@john:example.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If no match was found:
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
#### Bulk 3PID lookup
|
||||
- Method: `POST`
|
||||
- Content-Type: `application/json` (JSON)
|
||||
- Encoding: `UTF8`
|
||||
|
||||
##### Request Body
|
||||
```json
|
||||
{
|
||||
"lookup": [
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org"
|
||||
},
|
||||
{
|
||||
"medium": "msisdn",
|
||||
"address": "123456789"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### Response Body
|
||||
For all entries where a match was found:
|
||||
- `lookup[].id.type` supported values: `localpart`, `mxid`
|
||||
```json
|
||||
{
|
||||
"lookup": [
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org",
|
||||
"id": {
|
||||
"type": "localpart",
|
||||
"value": "john"
|
||||
}
|
||||
},
|
||||
{
|
||||
"medium": "msisdn",
|
||||
"address": "123456789",
|
||||
"id": {
|
||||
"type": "mxid",
|
||||
"value": "@jane:example.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If no match was found:
|
||||
```json
|
||||
{
|
||||
"lookup": []
|
||||
}
|
||||
```
|
||||
|
||||
### Profile
|
||||
#### Request Body
|
||||
For all requests, the values are the same:
|
||||
- Method: `POST`
|
||||
- Content-Type: `application/json` (JSON)
|
||||
- Encoding: `UTF8`
|
||||
|
||||
With body (example values):
|
||||
##### Request Body
|
||||
```json
|
||||
{
|
||||
"mxid": "@john.doe:example.org",
|
||||
"localpart": "john.doe",
|
||||
"domain": "example.org"
|
||||
}
|
||||
```
|
||||
#### Response Body
|
||||
For all responses, the same object structure will be parsed, making the non-relevant fields as optional.
|
||||
|
||||
Structure with example values:
|
||||
```json
|
||||
{
|
||||
"profile": {
|
||||
"display_name": "John Doe",
|
||||
"threepids": [
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "john.doe@example.org"
|
||||
},
|
||||
{
|
||||
"...": "..."
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"DomainUsers",
|
||||
"SalesOrg",
|
||||
"..."
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
The base `profile` key is mandatory. `display_name`, `threepids` and `roles` are only to be returned on the relevant request.
|
||||
|
||||
If there is no profile, the following response is expected:
|
||||
```json
|
||||
{
|
||||
"profile": {}
|
||||
}
|
||||
```
|
142
docs/stores/sql.md
Normal file
142
docs/stores/sql.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# SQL Identity store
|
||||
## Supported Databases
|
||||
- PostgreSQL
|
||||
- MariaDB
|
||||
- MySQL
|
||||
- SQLite
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | No |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | Yes |
|
||||
|
||||
Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication
|
||||
will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically
|
||||
the [Exec Identity Store](exec.md) or the [REST Identity Store](rest.md).
|
||||
|
||||
## Configuration
|
||||
### Basic
|
||||
```yaml
|
||||
sql:
|
||||
enabled: <boolean>
|
||||
```
|
||||
Enable/disable the identity store
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
sql:
|
||||
type: <string>
|
||||
```
|
||||
Set the SQL backend to use:
|
||||
- `sqlite`
|
||||
- `postgresql`
|
||||
- `mariadb`
|
||||
- `mysql`
|
||||
|
||||
### Connection
|
||||
#### SQLite
|
||||
```yaml
|
||||
sql:
|
||||
connection: <string>
|
||||
```
|
||||
Set the value to the absolute path to the Synapse SQLite DB file.
|
||||
Example: `/path/to/sqlite/file.db`
|
||||
|
||||
#### Others
|
||||
```yaml
|
||||
sql:
|
||||
connection: //<HOST[:PORT]/DB?user=USER&password=PASS
|
||||
```
|
||||
Set the connection info for the database by replacing the following values:
|
||||
- `HOST`: Hostname of the SQL server
|
||||
- `PORT`: Optional port value, if not default
|
||||
- `DB`: Database name
|
||||
- `USER`: Username for the connection
|
||||
- `PASS`: Password for the connection
|
||||
|
||||
This follow the JDBC URI syntax. See [official website](https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html#db_connection_url).
|
||||
|
||||
### Directory
|
||||
```yaml
|
||||
sql:
|
||||
directory:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
sql:
|
||||
directory:
|
||||
query:
|
||||
name:
|
||||
type: <string>
|
||||
value: <string>
|
||||
threepid:
|
||||
type: <string>
|
||||
value: <string>
|
||||
```
|
||||
For each query, `type` can be used to tell ma1sd how to process the ID column:
|
||||
- `localpart` will append the `matrix.domain` to it
|
||||
- `mxid` will use the ID as-is. If it is not a valid Matrix ID, the search will fail.
|
||||
|
||||
`value` is the SQL query and must return two columns:
|
||||
- The first being the User ID
|
||||
- The second being its display name
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
sql:
|
||||
directory:
|
||||
query:
|
||||
name:
|
||||
type: 'localpart'
|
||||
value: 'SELECT idColumn, displayNameColumn FROM table WHERE displayNameColumn LIKE ?'
|
||||
threepid:
|
||||
type: 'localpart'
|
||||
value: 'SELECT idColumn, displayNameColumn FROM table WHERE threepidColumn LIKE ?'
|
||||
```
|
||||
|
||||
### Identity
|
||||
**NOTE**: Only single lookup is supported. Bulk lookup always returns no mapping. This is a restriction as the Matrix API
|
||||
does not allow paging or otherwise limit of results of the API, potentially leading to thousands and thousands 3PIDs at once.
|
||||
|
||||
```yaml
|
||||
sql:
|
||||
identity:
|
||||
enabled: <boolean>
|
||||
type: <string>
|
||||
query: <string>
|
||||
medium:
|
||||
mediumTypeExample: <dedicated query>
|
||||
```
|
||||
`type` is used to tell ma1sd how to process the returned `uid` column containing the User ID:
|
||||
- `localpart` will build a full Matrix ID using the `matrix.domain` value.
|
||||
- `mxid` will use the ID as-is. If it is not a valid Matrix ID, lookup(s) will fail.
|
||||
|
||||
A specific query can also given per 3PID medium type.
|
||||
|
||||
### Profile
|
||||
```yaml
|
||||
sql:
|
||||
profile:
|
||||
enabled: <boolean>
|
||||
displayName:
|
||||
query: <string>
|
||||
threepid:
|
||||
query: <string>
|
||||
role:
|
||||
type: <string>
|
||||
query: <string>
|
||||
|
||||
|
||||
```
|
||||
For the `role` query, `type` can be used to tell ma1sd how to inject the User ID in the query:
|
||||
- `uid` will extract and set only the localpart.
|
||||
- `mxid` will use the ID as-is.
|
||||
|
||||
On each query, the first parameter `?` is set as a string with the corresponding ID format.
|
55
docs/stores/synapse.md
Normal file
55
docs/stores/synapse.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Synapse Identity Store
|
||||
Synapse's Database itself can be used as an Identity store. This identity store is a regular SQL store with
|
||||
built-in default queries that matches Synapse DB.
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | No |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | Yes |
|
||||
|
||||
- Authentication is done by Synapse itself.
|
||||
- Roles are mapped to communities. The Role name/ID uses the community ID in the form `+id:domain.tld`
|
||||
|
||||
## Configuration
|
||||
### Basic
|
||||
```yaml
|
||||
synapseSql:
|
||||
enabled: <boolean>
|
||||
```
|
||||
Enable/disable the identity store
|
||||
|
||||
---
|
||||
|
||||
```yaml
|
||||
synapseSql:
|
||||
type: <string>
|
||||
```
|
||||
Set the SQL backend to use which is configured in synapse:
|
||||
- `sqlite`
|
||||
- `postgresql`
|
||||
|
||||
### SQLite
|
||||
```yaml
|
||||
synapseSql:
|
||||
connection: <string>
|
||||
```
|
||||
Set the value to the absolute path to the Synapse SQLite DB file.
|
||||
Example: `/path/to/synapse/sqliteFile.db`
|
||||
|
||||
### PostgreSQL
|
||||
```yaml
|
||||
synapseSql:
|
||||
connection: //<HOST[:PORT]/DB?user=USER&password=PASS
|
||||
```
|
||||
Set the connection info for the database by replacing the following values:
|
||||
- `HOST`: Hostname of the SQL server
|
||||
- `PORT`: Optional port value, if not default
|
||||
- `DB`: Database name
|
||||
- `USER`: Username for the connection
|
||||
- `PASS`: Password for the connection
|
||||
|
||||
### Query customization
|
||||
See the [SQL Identity store](sql.md)
|
75
docs/stores/wordpress.md
Normal file
75
docs/stores/wordpress.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Wordpress Identity store
|
||||
This Identity store allows you to use user accounts registered on your Wordpress setup.
|
||||
Two types of connections are required for full support:
|
||||
- [REST API](https://developer.wordpress.org/rest-api/) with JWT authentication
|
||||
- Direct SQL access
|
||||
|
||||
## Features
|
||||
| Name | Supported |
|
||||
|-------------------------------------------------|-----------|
|
||||
| [Authentication](../features/authentication.md) | Yes |
|
||||
| [Directory](../features/directory.md) | Yes |
|
||||
| [Identity](../features/identity.md) | Yes |
|
||||
| [Profile](../features/profile.md) | No |
|
||||
|
||||
## Requirements
|
||||
- [Wordpress](https://wordpress.org/download/) >= 4.4
|
||||
- Permalink structure set to `Post Name`
|
||||
- [JWT Auth plugin for REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/)
|
||||
- SQL Credentials to the Wordpress Database
|
||||
|
||||
## Configuration
|
||||
### Wordpress
|
||||
#### JWT Auth
|
||||
Set a JWT secret into `wp-config.php` like so:
|
||||
```php
|
||||
define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');
|
||||
```
|
||||
`your-top-secret-key` should be set to a randomly generated value which is kept secret.
|
||||
|
||||
#### Rewrite of `index.php`
|
||||
Wordpress is normally configured with rewrite of `index.php` so it does not appear in URLs.
|
||||
If this is not the case for your installation, the ma1sd URL will need to be appended with `/index.php`
|
||||
|
||||
### ma1sd
|
||||
Enable in the configuration:
|
||||
```yaml
|
||||
wordpress:
|
||||
enabled: true
|
||||
```
|
||||
Configure the URL to your Wordpress installation - see above about added `/index.php`:
|
||||
```yaml
|
||||
wordpress:
|
||||
rest:
|
||||
base: 'http://localhost:8080'
|
||||
```
|
||||
Configure the SQL connection to your Wordpress database:
|
||||
```yaml
|
||||
wordpress:
|
||||
sql:
|
||||
connection: '//127.0.0.1/wordpress?user=root&password=example'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
By default, MySQL database is expected. If you use another database, use:
|
||||
```yaml
|
||||
wordpress:
|
||||
sql:
|
||||
type: <string>
|
||||
```
|
||||
With possible values:
|
||||
- `mysql`
|
||||
- `mariadb`
|
||||
- `postgresql`
|
||||
- `sqlite`
|
||||
|
||||
---
|
||||
|
||||
To configure the tables prefix for default queries, in case a custom value was set during Wordpress install:
|
||||
```yaml
|
||||
wordpress:
|
||||
sql:
|
||||
tablePrefix: <string>
|
||||
```
|
||||
By default, the value is set to `wp_`.
|
19
docs/threepids/medium/email/smtp-connector.md
Normal file
19
docs/threepids/medium/email/smtp-connector.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Email notifications - SMTP connector
|
||||
Connector ID: `smtp`
|
||||
|
||||
## Configuration
|
||||
```yaml
|
||||
threepid:
|
||||
medium:
|
||||
email:
|
||||
identity:
|
||||
from: 'identityServerEmail@example.org'
|
||||
name: 'My Identity Server'
|
||||
connectors:
|
||||
smtp:
|
||||
host: 'smtpHostname'
|
||||
tls: 1 # 0 = no STARTLS, 1 = try, 2 = force, 3 = TLS/SSL
|
||||
port: 587 # Set appropriate value depending on your TLS setting
|
||||
login: 'smtpLogin'
|
||||
password: 'smtpPassword'
|
||||
```
|
14
docs/threepids/medium/msisdn/twilio-connector.md
Normal file
14
docs/threepids/medium/msisdn/twilio-connector.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# SMS notifications - Twilio connector
|
||||
Connector ID: `twilio`
|
||||
|
||||
## Configuration
|
||||
```yaml
|
||||
threepid:
|
||||
medium:
|
||||
msisdn:
|
||||
connectors:
|
||||
twilio:
|
||||
account_sid: 'myAccountSid'
|
||||
auth_token: 'myAuthToken'
|
||||
number: '+123456789'
|
||||
```
|
40
docs/threepids/notification/basic-handler.md
Normal file
40
docs/threepids/notification/basic-handler.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Basic Notification handler
|
||||
Basic notification handler which uses two components:
|
||||
- Content generator, to produce the notifications
|
||||
- Connectors to send the notification content
|
||||
|
||||
This handler can be used with the 3PID types:
|
||||
- `email`
|
||||
- `msisdn` (Phone numbers)
|
||||
|
||||
## Generators
|
||||
- [Template](template-generator.md)
|
||||
## Connectors
|
||||
- Email
|
||||
- [SMTP](../medium/email/smtp-connector.md)
|
||||
- SMS
|
||||
- [Twilio](../medium/msisdn/twilio-connector.md)
|
||||
|
||||
## Configuration
|
||||
Enabled by default or with:
|
||||
```yaml
|
||||
notification:
|
||||
handler:
|
||||
email: 'raw'
|
||||
```
|
||||
|
||||
**WARNING:** Will be consolidated soon, prone to breaking changes.
|
||||
Structure and default values:
|
||||
```yaml
|
||||
threepid:
|
||||
medium:
|
||||
email:
|
||||
identity:
|
||||
from: ''
|
||||
name: ''
|
||||
connector: 'smtp'
|
||||
generator: 'template'
|
||||
msisdn:
|
||||
connector: 'twilio'
|
||||
generator: 'template'
|
||||
```
|
39
docs/threepids/notification/sendgrid-handler.md
Normal file
39
docs/threepids/notification/sendgrid-handler.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# SendGrid Notification handler
|
||||
> **WARNING:** This section is incomplete and may be misleading. Contact us if guidance is needed.
|
||||
|
||||
Enable with:
|
||||
```yaml
|
||||
notification:
|
||||
handler:
|
||||
email: 'sendgrid'
|
||||
```
|
||||
|
||||
Available Configuration keys:
|
||||
```yaml
|
||||
notification:
|
||||
handlers:
|
||||
sendgrid:
|
||||
api:
|
||||
key: <API key>
|
||||
identity:
|
||||
from: <Sender email address>
|
||||
name: <Sender name>
|
||||
templates:
|
||||
invite:
|
||||
subject: <Subject of the email notification sent for room invites>
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
||||
session:
|
||||
validation:
|
||||
subject: <Subject of the email notification sent for 3PID sessions>
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
||||
unbind:
|
||||
notification:
|
||||
subject: <Subject of the email notification sent for 3PID unbinds>
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
```
|
113
docs/threepids/notification/template-generator.md
Normal file
113
docs/threepids/notification/template-generator.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Notifications: Template generator
|
||||
Most of the Identity actions will trigger a notification of some kind, informing the user of some confirmation, next step
|
||||
or just informing them about the current state of things.
|
||||
|
||||
Those notifications are by default generated from templates and by replacing placeholder tokens in them with the relevant
|
||||
values of the notification. It is possible to customize the value of some placeholders, making easy to set values in the builtin templates, and/or
|
||||
provide your own custom templates.
|
||||
|
||||
Templates for the following events/actions are available:
|
||||
- [3PID invite](../../features/identity.md)
|
||||
- [3PID session: validation](../session/session.md)
|
||||
- [3PID session: unbind](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy#improving-your-privacy-one-commit-at-the-time)
|
||||
- [Matrix ID invite](../../features/experimental/application-service.md#email-notification-about-room-invites-by-matrix-ids)
|
||||
|
||||
## Placeholders
|
||||
All placeholders **MUST** be surrounded with `%` in the template. Per example, the `DOMAIN` placeholder would become
|
||||
`%DOMAIN%` within the template. This ensures replacement doesn't happen on non-placeholder strings.
|
||||
|
||||
### Global
|
||||
The following placeholders are available in every template:
|
||||
|
||||
| Placeholder | Purpose |
|
||||
|---------------------------------|------------------------------------------------------------------------------|
|
||||
| `DOMAIN` | Identity server authoritative domain, as configured in `matrix.domain` |
|
||||
| `DOMAIN_PRETTY` | Same as `DOMAIN` with the first letter upper case and all other lower case |
|
||||
| `FROM_EMAIL` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
|
||||
| `FROM_NAME` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
|
||||
| `RECIPIENT_MEDIUM` | The 3PID medium, like `email` or `msisdn` |
|
||||
| `RECIPIENT_MEDIUM_URL_ENCODED` | URL encoded value of `RECIPIENT_MEDIUM` |
|
||||
| `RECIPIENT_ADDRESS` | The address to which the notification is sent |
|
||||
| `RECIPIENT_ADDRESS_URL_ENCODED` | URL encoded value of `RECIPIENT_ADDRESS` |
|
||||
|
||||
### Room invitation
|
||||
Specific placeholders:
|
||||
|
||||
| Placeholder | Purpose |
|
||||
|------------------------------|-----------------------------------------------------------------------------------|
|
||||
| `SENDER_ID` | Matrix ID of the user who made the invite |
|
||||
| `SENDER_NAME` | Display name of the user who made the invite, if not available/set, empty |
|
||||
| `SENDER_NAME_OR_ID` | Display name of the user who made the invite. If not available/set, its Matrix ID |
|
||||
| `INVITE_MEDIUM` | The 3PID medium for the invite. |
|
||||
| `INVITE_MEDIUM_URL_ENCODED` | URL encoded value of `INVITE_MEDIUM` |
|
||||
| `INVITE_ADDRESS` | The 3PID address for the invite. |
|
||||
| `INVITE_ADDRESS_URL_ENCODED` | URL encoded value of `INVITE_ADDRESS` |
|
||||
| `ROOM_ID` | The Matrix ID of the Room in which the invite took place |
|
||||
| `ROOM_NAME` | The Name of the room in which the invite took place. If not available/set, empty |
|
||||
| `ROOM_NAME_OR_ID` | The Name of the room in which the invite took place. If not available/set, its ID |
|
||||
| `REGISTER_URL` | The URL to provide to the user allowing them to register their account, if needed |
|
||||
|
||||
### Validation of 3PID Session
|
||||
Specific placeholders:
|
||||
|
||||
| Placeholder | Purpose |
|
||||
|--------------------|--------------------------------------------------------------------------------------|
|
||||
| `VALIDATION_LINK` | URL, including token, to validate the 3PID session. |
|
||||
| `VALIDATION_TOKEN` | The token needed to validate the session, in case the user cannot use the link. |
|
||||
| `NEXT_URL` | URL to redirect to after the sessions has been validated. |
|
||||
|
||||
## Templates
|
||||
ma1sd comes with a set of builtin templates to easily get started. Those templates can be found
|
||||
[in the repository](https://github.com/ma1uta/ma1sd/tree/master/src/main/resources/threepids). If you want to use
|
||||
customized templates, we recommend using the builtin templates as a starting point.
|
||||
|
||||
> **NOTE**: The link above point to the latest version of the built-in templates. Those might be different from your
|
||||
version. Be sure to view the repo at the current tag.
|
||||
|
||||
## Configuration
|
||||
All configuration is specific to [3PID mediums](https://matrix.org/docs/spec/appendices.html#pid-types) and happen
|
||||
under the namespace `threepid.medium.<medium>.generators.template`.
|
||||
|
||||
Under such namespace, the following keys are available:
|
||||
- `invite`: Path to the 3PID invite notification template
|
||||
- `session.validation`: Path to the 3PID session validation notification template
|
||||
- `session.unbind`: Path to the 3PID session unbind notification template
|
||||
- `generic.matrixId`: Path to the Matrix ID invite notification template
|
||||
- `placeholder`: Map of key/values to set static values for some placeholders.
|
||||
|
||||
The `placeholder` map supports the following keys, mapped to their respective template placeholders:
|
||||
- `REGISTER_URL`
|
||||
|
||||
### Example
|
||||
#### Simple
|
||||
```yaml
|
||||
threepid:
|
||||
medium:
|
||||
email:
|
||||
generators:
|
||||
template:
|
||||
placeholder:
|
||||
REGISTER_URL: 'https://matrix-client.example.org'
|
||||
```
|
||||
In this configuration, the builtin templates are used and a static value for the `REGISTER_URL` is set, allowing to point
|
||||
a newly invited user to a webapp allowing the creation of its account on the server.
|
||||
|
||||
#### Advanced
|
||||
To configure paths to the various templates:
|
||||
```yaml
|
||||
threepid:
|
||||
medium:
|
||||
email:
|
||||
generators:
|
||||
template:
|
||||
invite: '/path/to/invite-template.eml'
|
||||
session:
|
||||
validation: '/path/to/validate-template.eml'
|
||||
unbind:
|
||||
notification: '/path/to/unbind-notification-template.eml'
|
||||
generic:
|
||||
matrixId: '/path/to/mxid-invite-template.eml'
|
||||
placeholder:
|
||||
REGISTER_URL: 'https://matrix-client.example.org'
|
||||
```
|
||||
In this configuration, a custom template is used for each event and a static value for the `REGISTER_URL` is set.
|
42
docs/threepids/session/session-views.md
Normal file
42
docs/threepids/session/session-views.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Web pages for the 3PID sessions
|
||||
You can customize the various pages used during a 3PID validation using the options below.
|
||||
|
||||
## Configuration
|
||||
Pseudo-configuration to illustrate the structure:
|
||||
```yaml
|
||||
# CONFIGURATION EXAMPLE
|
||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
||||
view:
|
||||
session:
|
||||
onTokenSubmit:
|
||||
success: '/path/to/session/tokenSubmitSuccess-page.html'
|
||||
failure: '/path/to/session/tokenSubmitFailure-page.html'
|
||||
# CONFIGURATION EXAMPLE
|
||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
||||
```
|
||||
|
||||
`view.session`:
|
||||
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
|
||||
link in a validation email.
|
||||
|
||||
The template should typically inform the user that the validation was successful and to go back in their Matrix client
|
||||
to finish the validation process, or that the validation failed.
|
||||
|
||||
Two configuration keys are available that accept paths to HTML templates:
|
||||
- `success`
|
||||
- `failure`
|
||||
|
||||
### Serving static assets
|
||||
ma1sd will not serve any static asset (images, JS, CSS, etc.). If such are needed, you will need to serve them using the
|
||||
reverse proxy sitting in front of ma1sd using a path outside of the `/_matrix/identity/` namespace. We advise using
|
||||
the base path `/static/` for such use cases, allowing to remain under the same hostname/origin.
|
||||
|
||||
You can also serve such assets using absolute URL, possibly under other domains, but be aware of Cross-Origin restrictions
|
||||
in browsers which are out of scope of ma1sd.
|
||||
|
||||
## Placeholders
|
||||
### Success
|
||||
No object/placeholder are currently available.
|
||||
|
||||
### Failure
|
||||
No object/placeholder are currently available.
|
143
docs/threepids/session/session.md
Normal file
143
docs/threepids/session/session.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# 3PID Sessions
|
||||
- [Overview](#overview)
|
||||
- [Restrictions](#restrictions)
|
||||
- [Bindings](#bindings)
|
||||
- [Federation](#federation)
|
||||
- [Notifications](#notifications)
|
||||
- [Email](#email)
|
||||
- [Phone numbers](#msisdn-(phone-numbers))
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Web views](#web-views)
|
||||
- [Scenarios](#scenarios)
|
||||
- [Sessions disabled](#sessions-disabled)
|
||||
|
||||
## Overview
|
||||
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client,
|
||||
the identity server is contacted to validate the 3PID.
|
||||
|
||||
To validate the 3PID, the identity server creates a session associated with a secret token. That token is sent via a message
|
||||
to the 3PID (e.g. an email) with a the necessary info so the user can submit them to the Identity Server, confirm ownership
|
||||
of the 3PID.
|
||||
|
||||
Once this 3PID is validated, the Homeserver will request that the Identity Server links the provided user Matrix ID with
|
||||
the 3PID session and finally add the 3PID to its own data store.
|
||||
|
||||
This serves two purposes:
|
||||
- Add the 3PID as an administrative/login info for the Homeserver directly
|
||||
- Links, called *Bind*, the 3PID so it can be queried from Homeservers and clients when inviting someone in a room
|
||||
by a 3PID, allowing it to be resolved to a Matrix ID.
|
||||
|
||||
## Restrictions
|
||||
### Bindings
|
||||
ma1sd does not store bindings directly. While a user can see its email, phone number or any other 3PID in its
|
||||
settings/profile, it does **NOT** mean it is published/saved anywhere or can be used to invite/search the user.
|
||||
|
||||
Identity stores are the ones holding such data, irrelevant if a user added a 3PID to their profile. When queried for
|
||||
bindings, ma1sd will query Identity stores which are responsible to store this kind of information.
|
||||
|
||||
Therefore, by default, any 3PID added to a user profile which is NOT within a configured and enabled Identity backend
|
||||
will simply not be usable for search or invites, **even on the same Homeserver!**
|
||||
|
||||
To have such 3PID bindings available for search and invite queries on synapse, use its dedicated
|
||||
[Identity store](../../stores/synapse.md).
|
||||
|
||||
### Federation
|
||||
In a federated set up, identity servers must cooperate to find the Matrix ID associated with a 3PID.
|
||||
|
||||
Federation is based on the principle that each server is responsible for its own (dns) domain.
|
||||
Therefore only those 3PID can be federated that can be distinguished by their
|
||||
domain such as email addresses.
|
||||
|
||||
Example: a user from Homeserver `example.org` adds an email `john@example.com`.
|
||||
Federated identity servers would try to find the identity server at `example.com` and ask it for the Matrix ID of associated with `john@example.com`.
|
||||
|
||||
Nevertheless, Matrix users might add 3PIDs that are not associated to a domain, for example telephone numbers.
|
||||
Or they might even add 3PIDs associated to a different domain (such as an email address hosted by Gmail).
|
||||
Such 3PIDs cannot be resolved in a federated way and will not be found from other servers.
|
||||
|
||||
Example: a user from Homeserver `example.org` adds an email `john@gmail.com`.
|
||||
If a federated lookup was performed, Identity servers would try to find the 3PID bind at the `gmail.com` server, and
|
||||
not `example.org`.
|
||||
|
||||
As ma1sd is built for self-hosted use cases, mainly for orgs/corps, this is usually not a problem for emails.
|
||||
Sadly, there is currently no mechanism to make this work for phone numbers.
|
||||
|
||||
## Notifications
|
||||
3PIDs are validated by sending a pre-formatted message containing a token to that 3PID address, which must be given to the
|
||||
Identity server that received the request. This is usually done by means of a URL to visit for email or a short number
|
||||
received by SMS for phone numbers.
|
||||
|
||||
ma1sd use two components for this:
|
||||
- Generator which produces the message to be sent with the necessary information the user needs to validate their session.
|
||||
- Connector which actually send the notification (e.g. SMTP for email).
|
||||
|
||||
Built-in generators and connectors for supported 3PID types:
|
||||
|
||||
### Email
|
||||
Generators:
|
||||
- [Template](../notification/template-generator.md)
|
||||
|
||||
Connectors:
|
||||
- [SMTP](../medium/email/smtp-connector.md)
|
||||
|
||||
#### MSISDN (Phone numbers)
|
||||
Generators:
|
||||
- [Template](../notification/template-generator.md)
|
||||
|
||||
Connectors:
|
||||
- [Twilio](../medium/msisdn/twilio-connector.md) with SMS
|
||||
|
||||
## Usage
|
||||
### Configuration
|
||||
The following example of configuration shows which items are relevant for 3PID sessions.
|
||||
|
||||
**IMPORTANT:** Most configuration items shown have default values and should not be included in your own configuration
|
||||
file unless you want to specifically overwrite them.
|
||||
```yaml
|
||||
# CONFIGURATION EXAMPLE
|
||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||
|
||||
session:
|
||||
policy:
|
||||
validation:
|
||||
enabled: true
|
||||
unbind:
|
||||
notifications: true
|
||||
enabled: true
|
||||
|
||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||
# CONFIGURATION EXAMPLE
|
||||
```
|
||||
|
||||
`session.policy.validation` is the core configuration to control what users configured to use your Identity server
|
||||
are allowed to do in terms of 3PID sessions. The policy has a global on/off switch for 3PID sessions using `.enabled`
|
||||
|
||||
---
|
||||
|
||||
`unbind` controls warning notifications for 3PID removal. Setting `notifications` for `unbind` to false will prevent unbind emails from sending.
|
||||
|
||||
### Web views
|
||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||
If the session or token is invalid, an error page is displayed.
|
||||
Workflow pages are also available for the remote 3PID session process.
|
||||
|
||||
See [the dedicated document](session-views.md)
|
||||
on how to configure/customize/brand those pages to your liking.
|
||||
|
||||
### Scenarios
|
||||
#### Sessions disabled
|
||||
This configuration would disable 3PID sessions altogether, preventing users from validating emails and/or phone numbers
|
||||
and any subsequent actions that requires them, like adding them to their profiles.
|
||||
|
||||
This would be used if ma1sd is also performing authentication for the Homeserver, typically with synapse and the
|
||||
[REST password provider](https://github.com/ma1uta/matrix-synapse-rest-password-provider), where 3PID mappings would be
|
||||
auto-populated.
|
||||
|
||||
Use the following values to enable this mode:
|
||||
```yaml
|
||||
session:
|
||||
policy:
|
||||
validation:
|
||||
enabled: false
|
||||
```
|
58
docs/troubleshooting.md
Normal file
58
docs/troubleshooting.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Troubleshooting
|
||||
- [Purpose](#purpose)
|
||||
- [Logs](#logs)
|
||||
- [Locations](#locations)
|
||||
- [Reading Them](#reading-them)
|
||||
- [Common issues](#common-issues)
|
||||
- [Submit an issue](#submit-an-issue)
|
||||
|
||||
## Purpose
|
||||
This document describes basic troubleshooting steps for ma1sd.
|
||||
|
||||
## Logs
|
||||
### Locations
|
||||
ma1sd logs to `STDOUT` (Standard Output) and `STDERR` (Standard Error) only, which gets redirected
|
||||
to log file(s) depending on your system.
|
||||
|
||||
If you use the [Debian package](install/debian.md), this goes to `syslog`.
|
||||
If you use the [Docker image](install/docker.md), this goes to the container logs.
|
||||
|
||||
For any other platform, please refer to your package maintainer.
|
||||
|
||||
### Increase verbosity
|
||||
To increase log verbosity and better track issues, the following means are available:
|
||||
- Add the `-v` command line parameter
|
||||
- Use the environment variable and value `MA1SD_LOG_LEVEL=debug`
|
||||
|
||||
### Reading them
|
||||
Before reporting an issue, it is important to produce clean and complete logs so they can be understood.
|
||||
|
||||
It is usually useless to try to troubleshoot an issue based on a single log line. Any action or API request
|
||||
in ma1sd would trigger more than one log lines, and those would be considered necessary context to
|
||||
understand what happened.
|
||||
|
||||
You may also find things called *stacktraces*. Those are important to pin-point bugs and the likes and should
|
||||
always be included in any report. They also tend to be very specific about the issue at hand.
|
||||
|
||||
Example of a stacktrace:
|
||||
```
|
||||
Exception in thread "main" java.lang.NullPointerException
|
||||
at com.example.myproject.Book.getTitle(Book.java:16)
|
||||
at com.example.myproject.Author.getBookTitles(Author.java:25)
|
||||
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
|
||||
```
|
||||
|
||||
### Common issues
|
||||
#### Internal Server Error
|
||||
`Contact your administrator with reference Transaction #123456789`
|
||||
|
||||
This is a generic message produced in case of an unknown error. The transaction reference allows to easily find
|
||||
the location in the logs to look for an error.
|
||||
|
||||
**IMPORTANT:** That line alone does not tell you anything about the error. You'll need the log lines before and after,
|
||||
usually including a stacktrace, to know what happened. Please take the time to read the surround output to get
|
||||
context about the issue at hand.
|
||||
|
||||
## Submit an issue
|
||||
In case the logs do not allow you to understand the issue at hand, please submit clean and complete logs
|
||||
as explained [here](#reading-them) in a new issue on the repository, or [get in touch](../README.md#contact).
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
||||
#Fri Aug 11 17:19:02 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.2-bin.zip
|
||||
|
313
gradlew
vendored
313
gradlew
vendored
@@ -1,78 +1,129 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# 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.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -81,92 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
80
gradlew.bat
vendored
80
gradlew.bat
vendored
@@ -1,4 +1,22 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -9,25 +27,29 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -35,48 +57,36 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
219
mxids.example.yaml
Normal file
219
mxids.example.yaml
Normal file
@@ -0,0 +1,219 @@
|
||||
# Sample configuration file explaining the minimum required keys to be set to run ma1sd
|
||||
#
|
||||
# For a complete list of options, see https://github.com/ma1uta/ma1sd/docs/README.md
|
||||
#
|
||||
# Please follow the Getting Started guide if this is your first time using/configuring ma1sd
|
||||
#
|
||||
# -- https://github.com/ma1uta/ma1sd/blob/master/docs/getting-started.md#getting-started
|
||||
#
|
||||
|
||||
#######################
|
||||
# Matrix config items #
|
||||
#######################
|
||||
# Matrix domain, same as the domain configure in your Homeserver configuration.
|
||||
# NOTE: in Synapse Homeserver, the Matrix domain is defined as 'server_name' in configuration file.
|
||||
#
|
||||
# This is used to build the various identifiers in all the features.
|
||||
#
|
||||
# If the hostname of the public URL used to reach your Matrix services is different from your Matrix domain,
|
||||
# per example matrix.domain.tld vs domain.tld, then use the server.name configuration option.
|
||||
# See the "Configure" section of the Getting Started guide for more info.
|
||||
#
|
||||
matrix:
|
||||
domain: ''
|
||||
v1: false # deprecated
|
||||
v2: true # MSC2140 API v2. Riot require enabled V2 API.
|
||||
|
||||
|
||||
################
|
||||
# Signing keys #
|
||||
################
|
||||
# Absolute path for the Identity Server signing keys database.
|
||||
# /!\ THIS MUST **NOT** BE YOUR HOMESERVER KEYS FILE /!\
|
||||
# If this path does not exist, it will be auto-generated.
|
||||
#
|
||||
# During testing, /var/tmp/mxids/keys is a possible value
|
||||
# For production, recommended location shall be one of the following:
|
||||
# - /var/lib/mxids/keys
|
||||
# - /var/opt/mxids/keys
|
||||
# - /var/local/mxids/keys
|
||||
#
|
||||
key:
|
||||
path: ''
|
||||
|
||||
|
||||
# Path to the SQLite DB file for ma1sd internal storage
|
||||
# /!\ THIS MUST **NOT** BE YOUR HOMESERVER DATABASE /!\
|
||||
#
|
||||
# Examples:
|
||||
# - /var/opt/mxids/store.db
|
||||
# - /var/local/mxids/store.db
|
||||
# - /var/lib/mxids/store.db
|
||||
#
|
||||
storage:
|
||||
# backend: sqlite # or postgresql
|
||||
provider:
|
||||
sqlite:
|
||||
database: '/path/to/mxids.db'
|
||||
# postgresql:
|
||||
# # Wrap all string values with quotes to avoid yaml parsing mistakes
|
||||
# database: '//localhost/mxids' # or full variant //192.168.1.100:5432/mxids_database
|
||||
# username: 'mxids_user'
|
||||
# password: 'mxids_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 #
|
||||
###################
|
||||
# If you are using synapse standalone and do not have an Identity store,
|
||||
# see https://github.com/ma1uta/ma1sd/blob/master/docs/stores/synapse.md#synapse-identity-store
|
||||
#
|
||||
# If you would like to integrate with your AD/Samba/LDAP server,
|
||||
# see https://github.com/ma1uta/ma1sd/blob/master/docs/stores/ldap.md
|
||||
#
|
||||
# For any other Identity store, or to simply discover them,
|
||||
# see https://github.com/ma1uta/ma1sd/blob/master/docs/stores/README.md
|
||||
|
||||
|
||||
#################################################
|
||||
# Notifications for invites/addition to profile #
|
||||
#################################################
|
||||
# This is mandatory to deal with anything e-mail related.
|
||||
#
|
||||
# For an introduction to sessions, invites and 3PIDs in general,
|
||||
# see https://github.com/ma1uta/ma1sd/blob/master/docs/threepids/session/session.md#3pid-sessions
|
||||
#
|
||||
# If you would like to change the content of the notifications,
|
||||
# see https://github.com/ma1uta/ma1sd/blob/master/docs/threepids/notification/template-generator.md
|
||||
#
|
||||
#### E-mail connector
|
||||
threepid:
|
||||
medium:
|
||||
email:
|
||||
identity:
|
||||
# The e-mail to send as.
|
||||
from: "matrix-identity@example.org"
|
||||
|
||||
connectors:
|
||||
smtp:
|
||||
# SMTP host
|
||||
host: "smtp.example.org"
|
||||
|
||||
# TLS mode for the connection
|
||||
# Possible values:
|
||||
# 0 Disable any kind of TLS entirely
|
||||
# 1 Enable STARTLS if supported by server (default)
|
||||
# 2 Force STARTLS and fail if not available
|
||||
# 3 Use full TLS/SSL instead of STARTLS
|
||||
#
|
||||
tls: 1
|
||||
|
||||
# SMTP port
|
||||
# Be sure to adapt depending on your TLS choice, if changed from default
|
||||
port: 587
|
||||
|
||||
# Login for SMTP
|
||||
login: "matrix-identity@example.org"
|
||||
|
||||
# Password for the account
|
||||
password: "ThePassword"
|
||||
|
||||
|
||||
#### MSC2134 (hash lookup)
|
||||
|
||||
#hashing:
|
||||
# enabled: false # enable or disable the hash lookup MSC2140 (default is false)
|
||||
# pepperLength: 20 # length of the pepper value (default is 20)
|
||||
# rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
# hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
# algorithms:
|
||||
# - none # the same as v1 bulk lookup
|
||||
# - sha256 # hash the 3PID and pepper.
|
||||
# delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
# requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
|
||||
### hash lookup for synapseSql provider.
|
||||
# synapseSql:
|
||||
# lookup:
|
||||
# query: 'select user_id as mxid, medium, address from user_threepid_id_server' # query for retrive 3PIDs for hashes.
|
||||
# legacyRoomNames: false # use the old query to get room names.
|
||||
|
||||
### hash lookup for ldap provider (with example of the ldap configuration)
|
||||
# ldap:
|
||||
# enabled: true
|
||||
# lookup: true # hash lookup
|
||||
# activeDirectory: false
|
||||
# defaultDomain: ''
|
||||
# connection:
|
||||
# host: 'ldap.domain.tld'
|
||||
# port: 389
|
||||
# bindDn: 'cn=admin,dc=domain,dc=tld'
|
||||
# bindPassword: 'Secret'
|
||||
# baseDNs:
|
||||
# - 'dc=domain,dc=tld'
|
||||
# attribute:
|
||||
# uid:
|
||||
# type: 'uid' # or mxid
|
||||
# value: 'cn'
|
||||
# name: 'displayName'
|
||||
# identity:
|
||||
# filter: '(objectClass=inetOrgPerson)'
|
||||
|
||||
#### MSC2140 (Terms)
|
||||
#policy:
|
||||
# policies:
|
||||
# term_name: # term name
|
||||
# version: 1.0 # version
|
||||
# terms:
|
||||
# en: # lang
|
||||
# name: term name en # localized name
|
||||
# url: https://mxids.host.tld/term_en.html # localized url
|
||||
# fe: # lang
|
||||
# name: term name fr # localized name
|
||||
# url: https://mxids.host.tld/term_fr.html # localized url
|
||||
# regexp:
|
||||
# - '/_matrix/identity/v2/account.*'
|
||||
# - '/_matrix/identity/v2/hash_details'
|
||||
# - '/_matrix/identity/v2/lookup'
|
||||
#
|
||||
|
||||
# logging:
|
||||
# root: error # default level for all loggers (apps and thirdparty libraries)
|
||||
# app: info # log level only for the ma1sd
|
||||
# requests: false # or true to dump full requests and responses
|
||||
|
||||
|
||||
# Config invitation manager
|
||||
#invite:
|
||||
# fullDisplayName: true # print full name of the invited user (default false)
|
||||
# resolution:
|
||||
# timer: 10
|
||||
# period: seconds # search invites every 10 seconds (by default 5 minutes)
|
||||
|
||||
|
||||
# Internal API
|
||||
#internal:
|
||||
# enabled: true # default to false
|
11
renovate.json
Normal file
11
renovate.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"updateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
Package: mxisd
|
||||
Maintainer: Kamax.io <foss@kamax.io>
|
||||
Homepage: https://github.com/kamax-io/mxisd
|
||||
Package: mxids
|
||||
Maintainer: ma1uta <sablintolya@gmail.com>
|
||||
Homepage: https://git.cqre.net/cqrenet/mxids.git
|
||||
Description: Federated Matrix Identity Server
|
||||
Architecture: all
|
||||
Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless
|
||||
Section: net
|
||||
Priority: optional
|
||||
Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless | openjdk-11-jre | openjdk-11-jre-headless | openjdk-11-jdk | openjdk-11-jdk-headless
|
||||
Version: 0
|
||||
|
@@ -1,13 +1,19 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Add service account
|
||||
useradd -r mxisd || true
|
||||
useradd -r mxids || true
|
||||
|
||||
# Set permissions for data directory
|
||||
chown -R mxisd:mxisd %DEB_DATA_DIR%
|
||||
chown -R mxids:mxids %DEB_DATA_DIR%
|
||||
|
||||
# Create symlink to mxusd
|
||||
ln -sfT /usr/lib/mxisd/mxisd.jar /usr/bin/mxisd
|
||||
# Create symlink to mxids run script
|
||||
ln -sfT /usr/lib/mxids/mxids /usr/bin/mxids
|
||||
|
||||
# Enable systemd service
|
||||
systemctl enable mxisd.service
|
||||
systemctl enable mxids.service
|
||||
|
||||
# If we already have a config file setup, we attempt to run mxids automatically
|
||||
# Specifically targeted at upgrades where the service needs to be restarted
|
||||
if [ -f "%DEB_CONF_FILE%" ]; then
|
||||
systemctl restart mxids.service
|
||||
fi
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stop running instance if needed
|
||||
systemctl stop mxisd.service
|
||||
systemctl stop mxids.service
|
||||
|
||||
# Disable service if exists
|
||||
systemctl disable mxisd.service
|
||||
systemctl disable mxids.service
|
||||
|
||||
# remove symlink
|
||||
rm /usr/bin/mxisd
|
||||
rm /usr/bin/mxids
|
||||
|
@@ -1,2 +1,34 @@
|
||||
#!/bin/sh
|
||||
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -n "$CONF_FILE_PATH" ]] && [ ! -f "$CONF_FILE_PATH" ]; then
|
||||
echo "Generating config file $CONF_FILE_PATH"
|
||||
touch "CONF_FILE_PATH"
|
||||
|
||||
if [[ -n "$MATRIX_DOMAIN" ]]; then
|
||||
echo "Setting matrix domain to $MATRIX_DOMAIN"
|
||||
echo "matrix:" >> "$CONF_FILE_PATH"
|
||||
echo " domain: '$MATRIX_DOMAIN'" >> "$CONF_FILE_PATH"
|
||||
echo >> "$CONF_FILE_PATH"
|
||||
fi
|
||||
|
||||
if [[ -n "$SIGN_KEY_PATH" ]]; then
|
||||
echo "Setting signing key path to $SIGN_KEY_PATH"
|
||||
echo "key:" >> "$CONF_FILE_PATH"
|
||||
echo " path: '$SIGN_KEY_PATH'" >> "$CONF_FILE_PATH"
|
||||
echo >> "$CONF_FILE_PATH"
|
||||
fi
|
||||
|
||||
if [[ -n "$SQLITE_DATABASE_PATH" ]]; then
|
||||
echo "Setting SQLite DB path to $SQLITE_DATABASE_PATH"
|
||||
echo "storage:" >> "$CONF_FILE_PATH"
|
||||
echo " provider:" >> "$CONF_FILE_PATH"
|
||||
echo " sqlite:" >> "$CONF_FILE_PATH"
|
||||
echo " database: '$SQLITE_DATABASE_PATH'" >> "$CONF_FILE_PATH"
|
||||
echo >> "$CONF_FILE_PATH"
|
||||
fi
|
||||
|
||||
echo "Starting mxids..."
|
||||
echo
|
||||
fi
|
||||
|
||||
exec java -jar /app/mxids.jar -c /etc/mxids/mxids.yaml
|
||||
|
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AuthManager {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(AuthManager.class);
|
||||
|
||||
@Autowired
|
||||
private List<AuthenticatorProvider> providers = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private InvitationManager invMgr;
|
||||
|
||||
public UserAuthResult authenticate(String id, String password) {
|
||||
for (AuthenticatorProvider provider : providers) {
|
||||
if (!provider.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UserAuthResult result = provider.authenticate(id, password);
|
||||
if (result.isSuccess()) {
|
||||
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
||||
for (ThreePid pid : result.getThreePids()) {
|
||||
log.info("Processing {} for {}", pid, id);
|
||||
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid()));
|
||||
}
|
||||
|
||||
invMgr.lookupMappingsForInvites();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return new UserAuthResult().failure();
|
||||
}
|
||||
|
||||
}
|
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.auth.provider
|
||||
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.FirebaseOptions
|
||||
import com.google.firebase.auth.*
|
||||
import com.google.firebase.internal.NonNull
|
||||
import com.google.firebase.tasks.OnFailureListener
|
||||
import com.google.firebase.tasks.OnSuccessListener
|
||||
import io.kamax.matrix.ThreePidMedium
|
||||
import io.kamax.mxisd.auth.UserAuthResult
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
|
||||
|
||||
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); // FIXME use matrix-java-sdk
|
||||
|
||||
private boolean isEnabled;
|
||||
private String domain;
|
||||
private FirebaseApp fbApp;
|
||||
private FirebaseAuth fbAuth;
|
||||
|
||||
private void waitOnLatch(UserAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
|
||||
try {
|
||||
l.await(timeout, unit);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for " + purpose);
|
||||
result.failure();
|
||||
}
|
||||
}
|
||||
|
||||
public GoogleFirebaseAuthenticator(boolean isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public GoogleFirebaseAuthenticator(String credsPath, String db, String domain) {
|
||||
this(true);
|
||||
this.domain = domain;
|
||||
try {
|
||||
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider");
|
||||
fbAuth = FirebaseAuth.getInstance(fbApp);
|
||||
|
||||
log.info("Google Firebase Authentication is ready");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error when initializing Firebase", e);
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseCredential getCreds(String credsPath) throws IOException {
|
||||
if (StringUtils.isNotBlank(credsPath)) {
|
||||
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
|
||||
} else {
|
||||
return FirebaseCredentials.applicationDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseOptions getOpts(String credsPath, String db) throws IOException {
|
||||
if (StringUtils.isBlank(db)) {
|
||||
throw new IllegalArgumentException("Firebase database is not configured");
|
||||
}
|
||||
|
||||
return new FirebaseOptions.Builder()
|
||||
.setCredential(getCreds(credsPath))
|
||||
.setDatabaseUrl(db)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
private void waitOnLatch(CountDownLatch l) {
|
||||
try {
|
||||
l.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for Firebase auth check");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuthResult authenticate(String id, String password) {
|
||||
if (!isEnabled()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
final UserAuthResult result = new UserAuthResult();
|
||||
|
||||
log.info("Trying to authenticate {}", id);
|
||||
Matcher m = matrixIdLaxPattern.matcher(id);
|
||||
if (!m.matches()) {
|
||||
log.warn("Could not validate {} as a Matrix ID", id);
|
||||
result.failure();
|
||||
}
|
||||
|
||||
String localpart = m.group(1);
|
||||
|
||||
CountDownLatch l = new CountDownLatch(1);
|
||||
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
|
||||
@Override
|
||||
void onSuccess(FirebaseToken token) {
|
||||
try {
|
||||
if (!StringUtils.equals(localpart, token.getUid())) {
|
||||
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
|
||||
result.failure();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("{} was successfully authenticated", id);
|
||||
result.success(id, token.getName());
|
||||
|
||||
log.info("Fetching profile for {}", id);
|
||||
CountDownLatch userRecordLatch = new CountDownLatch(1);
|
||||
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
|
||||
@Override
|
||||
void onSuccess(UserRecord user) {
|
||||
try {
|
||||
if (StringUtils.isNotBlank(user.getEmail())) {
|
||||
result.withThreePid(ThreePidMedium.Email, user.getEmail());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(user.getPhoneNumber())) {
|
||||
result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber());
|
||||
}
|
||||
} finally {
|
||||
userRecordLatch.countDown();
|
||||
}
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
try {
|
||||
log.warn("Unable to fetch Firebase user profile for {}", id);
|
||||
result.failure();
|
||||
} finally {
|
||||
userRecordLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
waitOnLatch(result, userRecordLatch, 30, TimeUnit.SECONDS, "Firebase user profile");
|
||||
} finally {
|
||||
l.countDown()
|
||||
}
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
try {
|
||||
if (e instanceof IllegalArgumentException) {
|
||||
log.info("Failure to authenticate {}: invalid firebase token", id);
|
||||
} else {
|
||||
log.info("Failure to authenticate {}: {}", id, e.getMessage(), e);
|
||||
log.info("Exception", e);
|
||||
}
|
||||
|
||||
result.failure();
|
||||
} finally {
|
||||
l.countDown()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
waitOnLatch(result, l, 30, TimeUnit.SECONDS, "Firebase auth check");
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.auth.provider;
|
||||
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.mxisd.auth.UserAuthResult;
|
||||
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||
import io.kamax.mxisd.lookup.provider.LdapProvider;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.directory.api.ldap.model.cursor.CursorException;
|
||||
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
|
||||
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
|
||||
import org.apache.directory.api.ldap.model.entry.Attribute;
|
||||
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||
import org.apache.directory.api.ldap.model.message.SearchScope;
|
||||
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class LdapAuthProvider implements AuthenticatorProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
||||
|
||||
@Autowired
|
||||
private LdapConfig ldapCfg;
|
||||
|
||||
private LdapConnection getConn() {
|
||||
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls());
|
||||
}
|
||||
|
||||
private void bind(LdapConnection conn) throws LdapException {
|
||||
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword());
|
||||
}
|
||||
|
||||
private String getUidAttribute() {
|
||||
return ldapCfg.getAttribute().getUid().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return ldapCfg.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuthResult authenticate(String id, String password) {
|
||||
log.info("Performing auth for {}", id);
|
||||
|
||||
LdapConnection conn = getConn();
|
||||
try {
|
||||
bind(conn);
|
||||
|
||||
String uidType = ldapCfg.getAttribute().getUid().getType();
|
||||
MatrixID mxIdExt = new MatrixID(id);
|
||||
String userFilterValue = StringUtils.equals(LdapProvider.UID, uidType) ? mxIdExt.getLocalPart() : mxIdExt.getId();
|
||||
String userFilter = "(" + ldapCfg.getAttribute().getUid().getValue() + "=" + userFilterValue + ")";
|
||||
EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), ldapCfg.getAttribute().getName());
|
||||
try {
|
||||
while (cursor.next()) {
|
||||
Entry entry = cursor.get();
|
||||
String dn = entry.getDn().getName();
|
||||
log.info("Checking possible match, DN: {}", dn);
|
||||
|
||||
Attribute attribute = entry.get(getUidAttribute());
|
||||
if (attribute == null) {
|
||||
log.info("DN {}: no attribute {}, skpping", dn, getUidAttribute());
|
||||
continue;
|
||||
}
|
||||
|
||||
String data = attribute.get().toString();
|
||||
if (data.length() < 1) {
|
||||
log.info("DN {}: empty attribute {}, skipping", getUidAttribute());
|
||||
continue;
|
||||
}
|
||||
|
||||
log.info("Attempting authentication on LDAP for {}", dn);
|
||||
try {
|
||||
conn.bind(entry.getDn(), password);
|
||||
} catch (LdapException e) {
|
||||
log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage());
|
||||
return new UserAuthResult().failure();
|
||||
}
|
||||
|
||||
Attribute nameAttribute = entry.get(ldapCfg.getAttribute().getName());
|
||||
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
|
||||
|
||||
log.info("Authentication successful for {}", entry.getDn().getName());
|
||||
log.info("DN {} is a valid match", dn);
|
||||
|
||||
return new UserAuthResult().success(mxIdExt.getId(), name);
|
||||
}
|
||||
} catch (CursorLdapReferralException e) {
|
||||
log.warn("Entity for {} is only available via referral, skipping", mxIdExt);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
log.info("No match were found for {}", id);
|
||||
return new UserAuthResult().failure();
|
||||
} catch (LdapException | IOException | CursorException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "key")
|
||||
class KeyConfig implements InitializingBean {
|
||||
|
||||
private String path
|
||||
|
||||
void setPath(String path) {
|
||||
this.path = path
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return path
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
if (StringUtils.isBlank(getPath())) {
|
||||
throw new ConfigurationException("key.path")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lookup.recursive.bridge")
|
||||
class RecursiveLookupBridgeConfig implements InitializingBean {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class)
|
||||
|
||||
private boolean enabled
|
||||
private boolean recursiveOnly
|
||||
private String server
|
||||
private Map<String, String> mappings = new HashMap<>()
|
||||
|
||||
boolean getEnabled() {
|
||||
return enabled
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled
|
||||
}
|
||||
|
||||
boolean getRecursiveOnly() {
|
||||
return recursiveOnly
|
||||
}
|
||||
|
||||
void setRecursiveOnly(boolean recursiveOnly) {
|
||||
this.recursiveOnly = recursiveOnly
|
||||
}
|
||||
|
||||
String getServer() {
|
||||
return server
|
||||
}
|
||||
|
||||
void setServer(String server) {
|
||||
this.server = server
|
||||
}
|
||||
|
||||
Map<String, String> getMappings() {
|
||||
return mappings
|
||||
}
|
||||
|
||||
void setMappings(Map<String, String> mappings) {
|
||||
this.mappings = mappings
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
log.info("--- Bridge integration lookups config ---")
|
||||
log.info("Enabled: {}", getEnabled())
|
||||
if (getEnabled()) {
|
||||
log.info("Recursive only: {}", getRecursiveOnly())
|
||||
log.info("Fallback Server: {}", getServer())
|
||||
log.info("Mappings: {}", mappings.size())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "lookup.recursive")
|
||||
class RecursiveLookupConfig {
|
||||
|
||||
private boolean enabled
|
||||
private List<String> allowedCidr
|
||||
private RecursiveLookupBridgeConfig bridge
|
||||
|
||||
boolean isEnabled() {
|
||||
return enabled
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled
|
||||
}
|
||||
|
||||
List<String> getAllowedCidr() {
|
||||
return allowedCidr
|
||||
}
|
||||
|
||||
void setAllowedCidr(List<String> allowedCidr) {
|
||||
this.allowedCidr = allowedCidr
|
||||
}
|
||||
|
||||
RecursiveLookupBridgeConfig getBridge() {
|
||||
return bridge
|
||||
}
|
||||
|
||||
void setBridge(RecursiveLookupBridgeConfig bridge) {
|
||||
this.bridge = bridge
|
||||
}
|
||||
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "server")
|
||||
class ServerConfig implements InitializingBean {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(ServerConfig.class);
|
||||
|
||||
private String name
|
||||
private int port
|
||||
private String publicUrl
|
||||
|
||||
String getName() {
|
||||
return name
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return port
|
||||
}
|
||||
|
||||
void setPort(int port) {
|
||||
this.port = port
|
||||
}
|
||||
|
||||
String getPublicUrl() {
|
||||
return publicUrl
|
||||
}
|
||||
|
||||
void setPublicUrl(String publicUrl) {
|
||||
this.publicUrl = publicUrl
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
if (StringUtils.isBlank(getName())) {
|
||||
throw new ConfigurationException("server.name")
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(getPublicUrl())) {
|
||||
log.warn("Public URL is empty, generating from name {}", getName())
|
||||
publicUrl = "https://${getName()}"
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(getPublicUrl())
|
||||
} catch (MalformedURLException e) {
|
||||
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"))
|
||||
}
|
||||
|
||||
log.info("--- Server config ---")
|
||||
log.info("Name: {}", getName())
|
||||
log.info("Port: {}", getPort())
|
||||
log.info("Public URL: {}", getPublicUrl())
|
||||
}
|
||||
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties("storage")
|
||||
public class StorageConfig {
|
||||
|
||||
private String backend;
|
||||
|
||||
public String getBackend() {
|
||||
return backend;
|
||||
}
|
||||
|
||||
public void setBackend(String backend) {
|
||||
this.backend = backend;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
if (StringUtils.isBlank(getBackend())) {
|
||||
throw new ConfigurationException("storage.backend");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config.invite.sender;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "invite.sender.email")
|
||||
public class EmailSenderConfig {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(EmailSenderConfig.class);
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
private int tls;
|
||||
private String login;
|
||||
private String password;
|
||||
private String email;
|
||||
private String name;
|
||||
private String template;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getTls() {
|
||||
return tls;
|
||||
}
|
||||
|
||||
public void setTls(int tls) {
|
||||
this.tls = tls;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public void setTemplate(String template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
log.info("--- E-mail Invite Sender config ---");
|
||||
log.info("Host: {}", getHost());
|
||||
log.info("Port: {}", getPort());
|
||||
log.info("TLS Mode: {}", getTls());
|
||||
log.info("Login: {}", getLogin());
|
||||
log.info("Has password: {}", StringUtils.isBlank(getPassword()));
|
||||
log.info("E-mail: {}", getEmail());
|
||||
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
|
||||
if (StringUtils.isBlank(getTemplate())) {
|
||||
log.warn("invite.sender.template is empty! Will not send invites");
|
||||
} else {
|
||||
File cp = new File(getTemplate()).getAbsoluteFile();
|
||||
log.info("Template: {}", cp.getAbsolutePath());
|
||||
if (!cp.exists() || !cp.isFile() || !cp.canRead()) {
|
||||
log.warn(getTemplate() + " does not exist, is not a file or cannot be read");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Template: Built-in");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config.ldap
|
||||
|
||||
import groovy.json.JsonOutput
|
||||
import io.kamax.mxisd.lookup.provider.LdapProvider
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
import javax.annotation.PostConstruct
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ldap")
|
||||
class LdapConfig {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(LdapConfig.class)
|
||||
|
||||
private boolean enabled
|
||||
|
||||
@Autowired
|
||||
private LdapConnectionConfig conn
|
||||
private LdapAttributeConfig attribute
|
||||
private LdapAuthConfig auth
|
||||
private LdapIdentityConfig identity
|
||||
|
||||
boolean isEnabled() {
|
||||
return enabled
|
||||
}
|
||||
|
||||
void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled
|
||||
}
|
||||
|
||||
LdapConnectionConfig getConn() {
|
||||
return conn
|
||||
}
|
||||
|
||||
void setConn(LdapConnectionConfig conn) {
|
||||
this.conn = conn
|
||||
}
|
||||
|
||||
LdapAttributeConfig getAttribute() {
|
||||
return attribute
|
||||
}
|
||||
|
||||
void setAttribute(LdapAttributeConfig attribute) {
|
||||
this.attribute = attribute
|
||||
}
|
||||
|
||||
LdapAuthConfig getAuth() {
|
||||
return auth
|
||||
}
|
||||
|
||||
void setAuth(LdapAuthConfig auth) {
|
||||
this.auth = auth
|
||||
}
|
||||
|
||||
LdapIdentityConfig getIdentity() {
|
||||
return identity
|
||||
}
|
||||
|
||||
void setIdentity(LdapIdentityConfig identity) {
|
||||
this.identity = identity
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void afterPropertiesSet() {
|
||||
log.info("--- LDAP Config ---")
|
||||
log.info("Enabled: {}", isEnabled())
|
||||
|
||||
if (!isEnabled()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(conn.getHost())) {
|
||||
throw new IllegalStateException("LDAP Host must be configured!")
|
||||
}
|
||||
|
||||
if (1 > conn.getPort() || 65535 < conn.getPort()) {
|
||||
throw new IllegalStateException("LDAP port is not valid")
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(attribute.getUid().getType())) {
|
||||
throw new IllegalStateException("Attribute UID Type cannot be empty")
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.isBlank(attribute.getUid().getValue())) {
|
||||
throw new IllegalStateException("Attribute UID value cannot be empty")
|
||||
}
|
||||
|
||||
String uidType = attribute.getUid().getType();
|
||||
if (!StringUtils.equals(LdapProvider.UID, uidType) && !StringUtils.equals(LdapProvider.MATRIX_ID, uidType)) {
|
||||
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType)
|
||||
}
|
||||
|
||||
log.info("Host: {}", conn.getHost())
|
||||
log.info("Port: {}", conn.getPort())
|
||||
log.info("Bind DN: {}", conn.getBindDn())
|
||||
log.info("Base DN: {}", conn.getBaseDn())
|
||||
|
||||
log.info("Attribute: {}", JsonOutput.toJson(attribute))
|
||||
log.info("Auth: {}", JsonOutput.toJson(auth))
|
||||
log.info("Identity: {}", JsonOutput.toJson(identity))
|
||||
}
|
||||
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config.ldap;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ldap.connection")
|
||||
public class LdapConnectionConfig {
|
||||
|
||||
private boolean tls;
|
||||
private String host;
|
||||
private int port;
|
||||
private String bindDn;
|
||||
private String bindPassword;
|
||||
private String baseDn;
|
||||
|
||||
public boolean isTls() {
|
||||
return tls;
|
||||
}
|
||||
|
||||
public void setTls(boolean tls) {
|
||||
this.tls = tls;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getBindDn() {
|
||||
return bindDn;
|
||||
}
|
||||
|
||||
public void setBindDn(String bindDn) {
|
||||
this.bindDn = bindDn;
|
||||
}
|
||||
|
||||
public String getBindPassword() {
|
||||
return bindPassword;
|
||||
}
|
||||
|
||||
public void setBindPassword(String bindPassword) {
|
||||
this.bindPassword = bindPassword;
|
||||
}
|
||||
|
||||
public String getBaseDn() {
|
||||
return baseDn;
|
||||
}
|
||||
|
||||
public void setBaseDn(String baseDn) {
|
||||
this.baseDn = baseDn;
|
||||
}
|
||||
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.kamax.mxisd.auth.AuthManager;
|
||||
import io.kamax.mxisd.auth.UserAuthResult;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public class AuthController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(AuthController.class);
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
@Autowired
|
||||
private AuthManager mgr;
|
||||
|
||||
@RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST)
|
||||
public String checkCredentials(HttpServletRequest req) {
|
||||
try {
|
||||
JsonElement el = new JsonParser().parse(IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8));
|
||||
if (!el.isJsonObject() || !el.getAsJsonObject().has("user")) {
|
||||
throw new IllegalArgumentException("Missing user key");
|
||||
}
|
||||
|
||||
JsonObject authData = el.getAsJsonObject().get("user").getAsJsonObject();
|
||||
if (!authData.has("id") || !authData.has("password")) {
|
||||
throw new IllegalArgumentException("Missing id or password keys");
|
||||
}
|
||||
|
||||
String id = authData.get("id").getAsString();
|
||||
log.info("Requested to check credentials for {}", id);
|
||||
String password = authData.get("password").getAsString();
|
||||
|
||||
UserAuthResult result = mgr.authenticate(id, password);
|
||||
|
||||
JsonObject authObj = new JsonObject();
|
||||
authObj.addProperty("success", result.isSuccess());
|
||||
if (result.isSuccess()) {
|
||||
authObj.addProperty("mxid", result.getMxid());
|
||||
authObj.addProperty("display_name", result.getDisplayName());
|
||||
}
|
||||
JsonObject obj = new JsonObject();
|
||||
|
||||
obj.add("authentication", authObj);
|
||||
return gson.toJson(obj);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@ControllerAdvice
|
||||
@ResponseBody
|
||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public class DefaultExceptionHandler {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
static String handle(String erroCode, String error) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("errcode", erroCode);
|
||||
obj.addProperty("error", error);
|
||||
return gson.toJson(obj);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public String handle(MissingServletRequestParameterException e) {
|
||||
return handle("M_INVALID_BODY", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MappingAlreadyExistsException.class)
|
||||
public String handle(MappingAlreadyExistsException e) {
|
||||
return handle("M_ALREADY_EXISTS", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public String handle(BadRequestException e) {
|
||||
return handle("M_BAD_REQUEST", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public String handle(HttpServletRequest req, RuntimeException e) {
|
||||
log.error("Unknown error when handling {}", req.getRequestURL(), e);
|
||||
return handle("M_UNKNOWN", StringUtils.defaultIfBlank(e.getMessage(), "An uknown error occured. Contact the server administrator if this persists."));
|
||||
}
|
||||
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.kamax.matrix.MatrixID
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO
|
||||
import io.kamax.mxisd.invitation.IThreePidInvite
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply
|
||||
import io.kamax.mxisd.invitation.InvitationManager
|
||||
import io.kamax.mxisd.invitation.ThreePidInvite
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.CrossOrigin
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class InvitationController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(InvitationController.class)
|
||||
|
||||
@Autowired
|
||||
private InvitationManager mgr
|
||||
|
||||
@Autowired
|
||||
private KeyManager keyMgr
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg
|
||||
|
||||
private Gson gson = new Gson()
|
||||
|
||||
@RequestMapping(value = "/store-invite", method = POST)
|
||||
String store(
|
||||
HttpServletRequest request,
|
||||
@RequestParam String sender,
|
||||
@RequestParam String medium,
|
||||
@RequestParam String address,
|
||||
@RequestParam("room_id") String roomId) {
|
||||
Map<String, String> parameters = new HashMap<>()
|
||||
for (String key : request.getParameterMap().keySet()) {
|
||||
parameters.put(key, request.getParameter(key));
|
||||
}
|
||||
IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters)
|
||||
IThreePidInviteReply reply = mgr.storeInvite(invite)
|
||||
|
||||
return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl()))
|
||||
}
|
||||
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import groovy.json.JsonOutput
|
||||
import io.kamax.mxisd.exception.BadRequestException
|
||||
import io.kamax.mxisd.exception.NotImplementedException
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class KeyController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(KeyController.class)
|
||||
|
||||
@Autowired
|
||||
private KeyManager keyMgr
|
||||
|
||||
@RequestMapping(value = "/pubkey/{keyType}:{keyId}", method = GET)
|
||||
String getKey(@PathVariable String keyType, @PathVariable int keyId) {
|
||||
if (!"ed25519".contentEquals(keyType)) {
|
||||
throw new BadRequestException("Invalid algorithm: " + keyType)
|
||||
}
|
||||
|
||||
log.info("Key {}:{} was requested", keyType, keyId)
|
||||
return JsonOutput.toJson([
|
||||
public_key: keyMgr.getPublicKeyBase64(keyId)
|
||||
])
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
|
||||
String checkEphemeralKeyValidity(HttpServletRequest request) {
|
||||
log.error("{} was requested but not implemented", request.getRequestURL())
|
||||
|
||||
throw new NotImplementedException()
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/pubkey/isvalid", method = GET)
|
||||
String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) {
|
||||
log.info("Validating public key {}", pubKey)
|
||||
|
||||
// TODO do in manager
|
||||
boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))
|
||||
return JsonOutput.toJson(
|
||||
valid: valid
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.JsonSlurper
|
||||
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson
|
||||
import io.kamax.mxisd.lookup.*
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy
|
||||
import io.kamax.mxisd.signature.SignatureManager
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.CrossOrigin
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class MappingController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(MappingController.class)
|
||||
private JsonSlurper json = new JsonSlurper()
|
||||
private Gson gson = new Gson()
|
||||
|
||||
@Autowired
|
||||
private LookupStrategy strategy
|
||||
|
||||
@Autowired
|
||||
private SignatureManager signMgr
|
||||
|
||||
private void setRequesterInfo(ALookupRequest lookupReq, HttpServletRequest req) {
|
||||
lookupReq.setRequester(req.getRemoteAddr())
|
||||
String xff = req.getHeader("X-FORWARDED-FOR")
|
||||
lookupReq.setRecursive(StringUtils.isNotBlank(xff))
|
||||
if (lookupReq.isRecursive()) {
|
||||
lookupReq.setRecurseHosts(Arrays.asList(xff.split(",")))
|
||||
}
|
||||
|
||||
lookupReq.setUserAgent(req.getHeader("USER-AGENT"))
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/lookup", method = GET)
|
||||
String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) {
|
||||
SingleLookupRequest lookupRequest = new SingleLookupRequest()
|
||||
setRequesterInfo(lookupRequest, request)
|
||||
lookupRequest.setType(medium)
|
||||
lookupRequest.setThreePid(address)
|
||||
|
||||
log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
|
||||
|
||||
Optional<SingleLookupReply> lookupOpt = strategy.find(lookupRequest)
|
||||
if (!lookupOpt.isPresent()) {
|
||||
log.info("No mapping was found, return empty JSON object")
|
||||
return JsonOutput.toJson([])
|
||||
}
|
||||
|
||||
SingleLookupReply lookup = lookupOpt.get()
|
||||
if (lookup.isSigned()) {
|
||||
log.info("Lookup is already signed, sending as-is")
|
||||
return lookup.getBody();
|
||||
} else {
|
||||
log.info("Lookup is not signed, signing")
|
||||
JsonObject obj = new Gson().toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject()
|
||||
obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj)))
|
||||
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/bulk_lookup", method = POST)
|
||||
String bulkLookup(HttpServletRequest request) {
|
||||
BulkLookupRequest lookupRequest = new BulkLookupRequest()
|
||||
setRequesterInfo(lookupRequest, request)
|
||||
log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
|
||||
|
||||
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText())
|
||||
List<ThreePidMapping> mappings = new ArrayList<>()
|
||||
for (List<String> mappingRaw : input.getThreepids()) {
|
||||
ThreePidMapping mapping = new ThreePidMapping()
|
||||
mapping.setMedium(mappingRaw.get(0))
|
||||
mapping.setValue(mappingRaw.get(1))
|
||||
mappings.add(mapping)
|
||||
}
|
||||
lookupRequest.setMappings(mappings)
|
||||
|
||||
ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer()
|
||||
answer.addAll(strategy.find(lookupRequest))
|
||||
return JsonOutput.toJson(answer)
|
||||
}
|
||||
|
||||
}
|
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
|
||||
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
|
||||
import io.kamax.mxisd.exception.BadRequestException
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation
|
||||
import io.kamax.mxisd.mapping.MappingManager
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.apache.http.HttpStatus
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class SessionController {
|
||||
|
||||
@Autowired
|
||||
private MappingManager mgr
|
||||
|
||||
private Gson gson = new Gson()
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(SessionController.class)
|
||||
|
||||
private <T> T fromJson(HttpServletRequest req, Class<T> obj) {
|
||||
gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj)
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
if (StringUtils.equals("email", medium)) {
|
||||
SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class)
|
||||
return gson.toJson(new Sid(mgr.create(req)))
|
||||
}
|
||||
|
||||
if (StringUtils.equals("msisdn", medium)) {
|
||||
SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class)
|
||||
return gson.toJson(new Sid(mgr.create(req)))
|
||||
}
|
||||
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("errcode", "M_INVALID_3PID_TYPE")
|
||||
obj.addProperty("error", medium + " is not supported as a 3PID type")
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST)
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/submitToken")
|
||||
String validate(HttpServletRequest request,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret, @RequestParam String token) {
|
||||
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
mgr.validate(sid, secret, token)
|
||||
|
||||
return "{}"
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/getValidated3pid")
|
||||
String check(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret) {
|
||||
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
Optional<ThreePidValidation> result = mgr.getValidated(sid, secret)
|
||||
if (result.isPresent()) {
|
||||
log.info("requested session was validated")
|
||||
ThreePidValidation pid = result.get()
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("medium", pid.getMedium())
|
||||
obj.addProperty("address", pid.getAddress())
|
||||
obj.addProperty("validated_at", pid.getValidation().toEpochMilli())
|
||||
|
||||
return gson.toJson(obj);
|
||||
} else {
|
||||
log.info("requested session was not validated")
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
|
||||
obj.addProperty("error", "sid, secret or session not valid")
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST)
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/bind")
|
||||
String bind(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
|
||||
String data = IOUtils.toString(request.getReader())
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
try {
|
||||
mgr.bind(sid, secret, mxid)
|
||||
return "{}"
|
||||
} catch (BadRequestException e) {
|
||||
log.info("requested session was not validated")
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
|
||||
obj.addProperty("error", e.getMessage())
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST)
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
}
|
||||
|
||||
private class Sid {
|
||||
|
||||
private String sid;
|
||||
|
||||
public Sid(String sid) {
|
||||
setSid(sid);
|
||||
}
|
||||
|
||||
String getSid() {
|
||||
return sid
|
||||
}
|
||||
|
||||
void setSid(String sid) {
|
||||
this.sid = sid
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,325 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.invitation;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||
import io.kamax.mxisd.invitation.sender.IInviteSender;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.kamax.mxisd.signature.SignatureManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xbill.DNS.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class InvitationManager {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(InvitationManager.class);
|
||||
|
||||
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private IStorage storage;
|
||||
|
||||
@Autowired
|
||||
private LookupStrategy lookupMgr;
|
||||
|
||||
@Autowired
|
||||
private SignatureManager signMgr;
|
||||
|
||||
private Map<String, IInviteSender> senders;
|
||||
|
||||
private CloseableHttpClient client;
|
||||
private Gson gson;
|
||||
private Timer refreshTimer;
|
||||
|
||||
private String getId(IThreePidInvite invite) {
|
||||
return invite.getSender().getDomain() + invite.getMedium() + invite.getAddress();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
gson = new Gson();
|
||||
|
||||
log.info("Loading saved invites");
|
||||
Collection<ThreePidInviteIO> ioList = storage.getInvites();
|
||||
ioList.forEach(io -> {
|
||||
log.info("Processing invite {}", gson.toJson(io));
|
||||
ThreePidInvite invite = new ThreePidInvite(
|
||||
new MatrixID(io.getSender()),
|
||||
io.getMedium(),
|
||||
io.getAddress(),
|
||||
io.getRoomId(),
|
||||
io.getProperties()
|
||||
);
|
||||
|
||||
ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), "");
|
||||
invitations.put(reply.getId(), reply);
|
||||
});
|
||||
|
||||
// FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver
|
||||
try {
|
||||
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
|
||||
HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
|
||||
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
|
||||
client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
|
||||
} catch (Exception e) {
|
||||
// FIXME do better...
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
log.info("Setting up invitation mapping refresh timer");
|
||||
refreshTimer = new Timer();
|
||||
refreshTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
lookupMappingsForInvites();
|
||||
} catch (Throwable t) {
|
||||
log.error("Error when running background mapping refresh", t);
|
||||
}
|
||||
}
|
||||
}, 5000L, TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)); // FIXME make configurable
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private String getIdForLog(IThreePidInviteReply reply) {
|
||||
return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress();
|
||||
}
|
||||
|
||||
String getSrvRecordName(String domain) {
|
||||
return "_matrix._tcp." + domain;
|
||||
}
|
||||
|
||||
// TODO use caching mechanism
|
||||
// TODO export in matrix-java-sdk
|
||||
String findHomeserverForDomain(String domain) {
|
||||
log.debug("Performing SRV lookup for {}", domain);
|
||||
String lookupDns = getSrvRecordName(domain);
|
||||
log.info("Lookup name: {}", lookupDns);
|
||||
|
||||
try {
|
||||
List<SRVRecord> srvRecords = new ArrayList<>();
|
||||
Record[] rawRecords = new Lookup(lookupDns, Type.SRV).run();
|
||||
if (rawRecords != null && rawRecords.length > 0) {
|
||||
for (Record record : rawRecords) {
|
||||
if (Type.SRV == record.getType()) {
|
||||
srvRecords.add((SRVRecord) record);
|
||||
} else {
|
||||
log.info("Got non-SRV record: {}", record.toString());
|
||||
}
|
||||
}
|
||||
|
||||
srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority));
|
||||
for (SRVRecord record : srvRecords) {
|
||||
log.info("Found SRV record: {}", record.toString());
|
||||
return "https://" + record.getTarget().toString(true) + ":" + record.getPort();
|
||||
}
|
||||
} else {
|
||||
log.info("No SRV record for {}", lookupDns);
|
||||
}
|
||||
} catch (TextParseException e) {
|
||||
log.warn("Unable to perform DNS SRV query for {}: {}", lookupDns, e.getMessage());
|
||||
}
|
||||
|
||||
log.info("Performing basic lookup using domain name {}", domain);
|
||||
return "https://" + domain + ":8448";
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public InvitationManager(List<IInviteSender> senderList) {
|
||||
senders = new HashMap<>();
|
||||
senderList.forEach(sender -> senders.put(sender.getMedium(), sender));
|
||||
}
|
||||
|
||||
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
|
||||
IInviteSender sender = senders.get(invitation.getMedium());
|
||||
if (sender == null) {
|
||||
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
|
||||
}
|
||||
|
||||
String invId = getId(invitation);
|
||||
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId());
|
||||
if (invitations.containsKey(invId)) { // FIXME we need to lookup using the HS domain too!!
|
||||
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
|
||||
return invitations.get(invId);
|
||||
}
|
||||
|
||||
Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true);
|
||||
if (result.isPresent()) {
|
||||
log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress());
|
||||
throw new MappingAlreadyExistsException();
|
||||
}
|
||||
|
||||
String token = RandomStringUtils.randomAlphanumeric(64);
|
||||
String displayName = invitation.getAddress().substring(0, 3) + "...";
|
||||
|
||||
IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName);
|
||||
|
||||
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
|
||||
sender.send(reply);
|
||||
|
||||
log.info("Storing invite under ID {}", invId);
|
||||
storage.insertInvite(reply);
|
||||
invitations.put(invId, reply);
|
||||
log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender().getDomain());
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public void lookupMappingsForInvites() {
|
||||
log.info("Checking for existing mapping for pending invites");
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
log.info("Processing invite {}", getIdForLog(reply));
|
||||
ForkJoinPool.commonPool().submit(new MappingChecker(reply));
|
||||
}
|
||||
}
|
||||
|
||||
public void publishMappingIfInvited(ThreePidMapping threePid) {
|
||||
log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue());
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
if (StringUtils.equals(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equals(reply.getInvite().getAddress(), threePid.getValue())) {
|
||||
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain());
|
||||
publishMapping(reply, threePid.getMxid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void publishMapping(IThreePidInviteReply reply, String mxid) {
|
||||
String medium = reply.getInvite().getMedium();
|
||||
String address = reply.getInvite().getAddress();
|
||||
String domain = reply.getInvite().getSender().getDomain();
|
||||
log.info("Discovering HS for domain {}", domain);
|
||||
String hsUrlOpt = findHomeserverForDomain(domain);
|
||||
|
||||
// TODO this is needed as this will block if called during authentication cycle due to synapse implementation
|
||||
new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool
|
||||
HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind");
|
||||
// Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr
|
||||
JSONObject obj = new JSONObject(); // TODO use Gson instead
|
||||
obj.put("mxid", mxid);
|
||||
obj.put("token", reply.getToken());
|
||||
obj.put("signatures", signMgr.signMessageJson(obj.toString()));
|
||||
|
||||
JSONObject objUp = new JSONObject();
|
||||
objUp.put("mxid", mxid);
|
||||
objUp.put("medium", medium);
|
||||
objUp.put("address", address);
|
||||
objUp.put("sender", reply.getInvite().getSender().getId());
|
||||
objUp.put("room_id", reply.getInvite().getRoomId());
|
||||
objUp.put("signed", obj);
|
||||
|
||||
JSONObject content = new JSONObject(); // TODO use Gson instead
|
||||
JSONArray invites = new JSONArray();
|
||||
invites.put(objUp);
|
||||
content.put("invites", invites);
|
||||
content.put("medium", medium);
|
||||
content.put("address", address);
|
||||
content.put("mxid", mxid);
|
||||
|
||||
content.put("signatures", signMgr.signMessageJson(content.toString()));
|
||||
|
||||
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
|
||||
entity.setContentType("application/json");
|
||||
req.setEntity(entity);
|
||||
try {
|
||||
log.info("Posting onBind event to {}", req.getURI());
|
||||
CloseableHttpResponse response = client.execute(req);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
if (statusCode >= 300) {
|
||||
log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
invitations.remove(getId(reply.getInvite()));
|
||||
storage.deleteInvite(reply.getId());
|
||||
log.info("Removed invite from internal store");
|
||||
}
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Unable to tell HS {} about invite being mapped", domain, e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private class MappingChecker implements Runnable {
|
||||
|
||||
private IThreePidInviteReply reply;
|
||||
|
||||
public MappingChecker(IThreePidInviteReply reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
log.info("Searching for mapping created since invite {} was created", getIdForLog(reply));
|
||||
Optional<SingleLookupReply> result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true);
|
||||
if (result.isPresent()) {
|
||||
SingleLookupReply lookup = result.get();
|
||||
log.info("Found mapping for pending invite {}", getIdForLog(reply));
|
||||
publishMapping(reply, lookup.getMxid().getId());
|
||||
} else {
|
||||
log.info("No mapping for pending invite {}", getIdForLog(reply));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.error("Unable to process invite", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.invitation.sender;
|
||||
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.config.invite.sender.EmailSenderConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class EmailInviteSender implements IInviteSender {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(EmailInviteSender.class);
|
||||
|
||||
@Autowired
|
||||
private EmailSenderConfig cfg;
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext app;
|
||||
|
||||
private Session session;
|
||||
private InternetAddress sender;
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
try {
|
||||
session = Session.getInstance(System.getProperties());
|
||||
sender = new InternetAddress(cfg.getEmail(), cfg.getName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// What are we supposed to do with this?!
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return ThreePidMedium.Email.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(IThreePidInviteReply invite) {
|
||||
if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) {
|
||||
throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type");
|
||||
}
|
||||
|
||||
try {
|
||||
String domainPretty = WordUtils.capitalizeFully(srvCfg.getName());
|
||||
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
|
||||
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
||||
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
|
||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
||||
|
||||
String templateBody = IOUtils.toString(
|
||||
StringUtils.startsWith(cfg.getTemplate(), "classpath:") ?
|
||||
app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()),
|
||||
StandardCharsets.UTF_8);
|
||||
templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName());
|
||||
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
|
||||
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail());
|
||||
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());
|
||||
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
|
||||
templateBody = templateBody.replace("%SENDER_NAME%", senderName);
|
||||
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
|
||||
templateBody = templateBody.replace("%INVITE_MEDIUM%", invite.getInvite().getMedium());
|
||||
templateBody = templateBody.replace("%INVITE_ADDRESS%", invite.getInvite().getAddress());
|
||||
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
|
||||
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
|
||||
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
|
||||
|
||||
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8));
|
||||
msg.setHeader("X-Mailer", "mxisd"); // TODO set version
|
||||
msg.setSentDate(new Date());
|
||||
msg.setFrom(sender);
|
||||
msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress());
|
||||
msg.saveChanges();
|
||||
|
||||
log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort());
|
||||
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
|
||||
transport.setStartTLS(cfg.getTls() > 0);
|
||||
transport.setRequireStartTLS(cfg.getTls() > 1);
|
||||
|
||||
log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort());
|
||||
transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword());
|
||||
try {
|
||||
transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress()));
|
||||
log.info("Invite to {} was sent", invite.getInvite().getAddress());
|
||||
} finally {
|
||||
transport.close();
|
||||
}
|
||||
} catch (IOException | MessagingException e) {
|
||||
throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.key
|
||||
|
||||
import io.kamax.mxisd.config.KeyConfig
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
|
||||
@Component
|
||||
class KeyManager implements InitializingBean {
|
||||
|
||||
@Autowired
|
||||
private KeyConfig keyCfg
|
||||
|
||||
private EdDSAParameterSpec keySpecs
|
||||
private EdDSAEngine signEngine
|
||||
private List<KeyPair> keys
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
|
||||
signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm()))
|
||||
keys = new ArrayList<>()
|
||||
|
||||
Path privKey = Paths.get(keyCfg.getPath())
|
||||
|
||||
if (!Files.exists(privKey)) {
|
||||
KeyPair pair = (new KeyPairGenerator()).generateKeyPair()
|
||||
String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded())
|
||||
FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1)
|
||||
keys.add(pair)
|
||||
} else {
|
||||
if (Files.isDirectory(privKey)) {
|
||||
throw new RuntimeException("Invalid path for private key: ${privKey.toString()}")
|
||||
}
|
||||
|
||||
if (Files.isReadable(privKey)) {
|
||||
byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1))
|
||||
EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs)
|
||||
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs)
|
||||
keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getCurrentIndex() {
|
||||
return 0
|
||||
}
|
||||
|
||||
KeyPair getKeys(int index) {
|
||||
return keys.get(index)
|
||||
}
|
||||
|
||||
PrivateKey getPrivateKey(int index) {
|
||||
return getKeys(index).getPrivate()
|
||||
}
|
||||
|
||||
EdDSAPublicKey getPublicKey(int index) {
|
||||
return (EdDSAPublicKey) getKeys(index).getPublic()
|
||||
}
|
||||
|
||||
EdDSAParameterSpec getSpecs() {
|
||||
return keySpecs
|
||||
}
|
||||
|
||||
String getPublicKeyBase64(int index) {
|
||||
return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte())
|
||||
}
|
||||
|
||||
}
|
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
import org.xbill.DNS.Lookup
|
||||
import org.xbill.DNS.SRVRecord
|
||||
import org.xbill.DNS.Type
|
||||
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.RecursiveTask
|
||||
import java.util.function.Function
|
||||
|
||||
@Component
|
||||
class DnsLookupProvider implements IThreePidProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class)
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg
|
||||
|
||||
@Autowired
|
||||
private IRemoteIdentityServerFetcher fetcher
|
||||
|
||||
@Override
|
||||
boolean isEnabled() {
|
||||
return true
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLocal() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
int getPriority() {
|
||||
return 10
|
||||
}
|
||||
|
||||
String getSrvRecordName(String domain) {
|
||||
return "_matrix-identity._tcp." + domain
|
||||
}
|
||||
|
||||
Optional<String> getDomain(String email) {
|
||||
int atIndex = email.lastIndexOf("@")
|
||||
if (atIndex == -1) {
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
return Optional.of(email.substring(atIndex + 1))
|
||||
}
|
||||
|
||||
// TODO use caching mechanism
|
||||
Optional<String> findIdentityServerForDomain(String domain) {
|
||||
if (StringUtils.equals(srvCfg.getName(), domain)) {
|
||||
log.info("We are authoritative for {}, no remote lookup", domain)
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
log.info("Performing SRV lookup")
|
||||
String lookupDns = getSrvRecordName(domain)
|
||||
log.info("Lookup name: {}", lookupDns)
|
||||
|
||||
SRVRecord[] records = (SRVRecord[]) new Lookup(lookupDns, Type.SRV).run()
|
||||
if (records != null) {
|
||||
Arrays.sort(records, new Comparator<SRVRecord>() {
|
||||
|
||||
@Override
|
||||
int compare(SRVRecord o1, SRVRecord o2) {
|
||||
return Integer.compare(o1.getPriority(), o2.getPriority())
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
for (SRVRecord record : records) {
|
||||
log.info("Found SRV record: {}", record.toString())
|
||||
String baseUrl = "https://${record.getTarget().toString(true)}:${record.getPort()}"
|
||||
if (fetcher.isUsable(baseUrl)) {
|
||||
log.info("Found Identity Server for domain {} at {}", domain, baseUrl)
|
||||
return Optional.of(baseUrl)
|
||||
} else {
|
||||
log.info("{} is not a usable Identity Server", baseUrl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("No SRV record for {}", lookupDns)
|
||||
}
|
||||
|
||||
log.info("Performing basic lookup using domain name {}", domain)
|
||||
String baseUrl = "https://" + domain
|
||||
if (fetcher.isUsable(baseUrl)) {
|
||||
log.info("Found Identity Server for domain {} at {}", domain, baseUrl)
|
||||
return Optional.of(baseUrl)
|
||||
} else {
|
||||
log.info("{} is not a usable Identity Server", baseUrl)
|
||||
return Optional.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
if (!StringUtils.equals("email", request.getType())) { // TODO use enum
|
||||
log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid())
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
log.info("Performing DNS lookup for {}", request.getThreePid())
|
||||
|
||||
String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1)
|
||||
log.info("Domain name for {}: {}", request.getThreePid(), domain)
|
||||
Optional<String> baseUrl = findIdentityServerForDomain(domain)
|
||||
|
||||
if (baseUrl.isPresent()) {
|
||||
return fetcher.find(baseUrl.get(), request)
|
||||
}
|
||||
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
Map<String, List<ThreePidMapping>> domains = new HashMap<>()
|
||||
|
||||
for (ThreePidMapping mapping : mappings) {
|
||||
if (!StringUtils.equals("email", mapping.getMedium())) {
|
||||
log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue())
|
||||
continue
|
||||
}
|
||||
|
||||
Optional<String> domainOpt = getDomain(mapping.getValue())
|
||||
if (!domainOpt.isPresent()) {
|
||||
log.warn("No domain for 3PID {}", mapping.getValue())
|
||||
continue
|
||||
}
|
||||
|
||||
String domain = domainOpt.get()
|
||||
List<ThreePidMapping> domainMappings = domains.computeIfAbsent(domain, new Function<String, List<ThreePidMapping>>() {
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> apply(String s) {
|
||||
return new ArrayList<>()
|
||||
}
|
||||
|
||||
})
|
||||
domainMappings.add(mapping)
|
||||
}
|
||||
|
||||
log.info("Looking mappings across {} domains", domains.keySet().size())
|
||||
ForkJoinPool pool = new ForkJoinPool()
|
||||
RecursiveTask<List<ThreePidMapping>> task = new RecursiveTask<List<ThreePidMapping>>() {
|
||||
|
||||
@Override
|
||||
protected List<ThreePidMapping> compute() {
|
||||
List<ThreePidMapping> mappingsFound = new ArrayList<>()
|
||||
List<DomainBulkLookupTask> tasks = new ArrayList<>()
|
||||
|
||||
for (String domain : domains.keySet()) {
|
||||
DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain))
|
||||
domainTask.fork()
|
||||
tasks.add(domainTask)
|
||||
}
|
||||
|
||||
for (DomainBulkLookupTask task : tasks) {
|
||||
mappingsFound.addAll(task.join())
|
||||
}
|
||||
|
||||
return mappingsFound
|
||||
}
|
||||
}
|
||||
pool.submit(task)
|
||||
pool.shutdown()
|
||||
|
||||
List<ThreePidMapping> mappingsFound = task.join()
|
||||
log.info("Found {} mappings overall", mappingsFound.size())
|
||||
return mappingsFound
|
||||
}
|
||||
|
||||
private class DomainBulkLookupTask extends RecursiveTask<List<ThreePidMapping>> {
|
||||
|
||||
private String domain
|
||||
private List<ThreePidMapping> mappings
|
||||
|
||||
DomainBulkLookupTask(String domain, List<ThreePidMapping> mappings) {
|
||||
this.domain = domain
|
||||
this.mappings = mappings
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ThreePidMapping> compute() {
|
||||
List<ThreePidMapping> domainMappings = new ArrayList<>()
|
||||
|
||||
Optional<String> baseUrl = findIdentityServerForDomain(domain)
|
||||
if (!baseUrl.isPresent()) {
|
||||
log.info("No usable Identity server for domain {}", domain)
|
||||
} else {
|
||||
domainMappings.addAll(fetcher.find(baseUrl.get(), mappings))
|
||||
log.info("Found {} mappings in domain {}", domainMappings.size(), domain)
|
||||
}
|
||||
|
||||
return domainMappings
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ForwardConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class ForwarderProvider implements IThreePidProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(ForwarderProvider.class)
|
||||
|
||||
@Autowired
|
||||
private ForwardConfig cfg
|
||||
|
||||
@Autowired
|
||||
private IRemoteIdentityServerFetcher fetcher
|
||||
|
||||
@Override
|
||||
boolean isEnabled() {
|
||||
return true
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLocal() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
int getPriority() {
|
||||
return 0
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
for (String root : cfg.getServers()) {
|
||||
Optional<SingleLookupReply> answer = fetcher.find(root, request)
|
||||
if (answer.isPresent()) {
|
||||
return answer
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
List<ThreePidMapping> mappingsToDo = new ArrayList<>(mappings)
|
||||
List<ThreePidMapping> mappingsFoundGlobal = new ArrayList<>()
|
||||
|
||||
for (String root : cfg.getServers()) {
|
||||
log.info("{} mappings remaining: {}", mappingsToDo.size(), mappingsToDo)
|
||||
log.info("Querying {}", root)
|
||||
List<ThreePidMapping> mappingsFound = fetcher.find(root, mappingsToDo)
|
||||
log.info("{} returned {} mappings", root, mappingsFound.size())
|
||||
mappingsFoundGlobal.addAll(mappingsFound)
|
||||
mappingsToDo.removeAll(mappingsFound)
|
||||
}
|
||||
|
||||
return mappingsFoundGlobal
|
||||
}
|
||||
|
||||
}
|
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.FirebaseOptions
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import com.google.firebase.auth.FirebaseCredential
|
||||
import com.google.firebase.auth.FirebaseCredentials
|
||||
import com.google.firebase.auth.UserRecord
|
||||
import com.google.firebase.internal.NonNull
|
||||
import com.google.firebase.tasks.OnFailureListener
|
||||
import com.google.firebase.tasks.OnSuccessListener
|
||||
import io.kamax.matrix.ThreePidMedium
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public class GoogleFirebaseProvider implements IThreePidProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
|
||||
|
||||
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)");
|
||||
|
||||
private boolean isEnabled;
|
||||
private String domain;
|
||||
private FirebaseApp fbApp;
|
||||
private FirebaseAuth fbAuth;
|
||||
|
||||
public GoogleFirebaseProvider(boolean isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public GoogleFirebaseProvider(String credsPath, String db, String domain) {
|
||||
this(true);
|
||||
this.domain = domain;
|
||||
try {
|
||||
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider");
|
||||
fbAuth = FirebaseAuth.getInstance(fbApp);
|
||||
|
||||
log.info("Google Firebase Authentication is ready");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error when initializing Firebase", e);
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseCredential getCreds(String credsPath) throws IOException {
|
||||
if (StringUtils.isNotBlank(credsPath)) {
|
||||
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
|
||||
} else {
|
||||
return FirebaseCredentials.applicationDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseOptions getOpts(String credsPath, String db) throws IOException {
|
||||
if (StringUtils.isBlank(db)) {
|
||||
throw new IllegalArgumentException("Firebase database is not configured");
|
||||
}
|
||||
|
||||
return new FirebaseOptions.Builder()
|
||||
.setCredential(getCreds(credsPath))
|
||||
.setDatabaseUrl(db)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getMxid(UserRecord record) {
|
||||
return "@${record.getUid()}:${domain}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
private void waitOnLatch(CountDownLatch l) {
|
||||
try {
|
||||
l.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for Firebase auth check");
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UserRecord> findInternal(String medium, String address) {
|
||||
UserRecord r;
|
||||
CountDownLatch l = new CountDownLatch(1);
|
||||
|
||||
OnSuccessListener<UserRecord> success = new OnSuccessListener<UserRecord>() {
|
||||
@Override
|
||||
void onSuccess(UserRecord result) {
|
||||
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid())
|
||||
r = result;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
OnFailureListener failure = new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage())
|
||||
r = null;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
if (ThreePidMedium.Email.is(medium)) {
|
||||
log.info("Performing E-mail 3PID lookup for {}", address)
|
||||
fbAuth.getUserByEmail(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
|
||||
log.info("Performing msisdn 3PID lookup for {}", address)
|
||||
fbAuth.getUserByPhoneNumber(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else {
|
||||
log.info("{} is not a supported 3PID medium", medium);
|
||||
r = null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid())
|
||||
if (urOpt.isPresent()) {
|
||||
return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get())));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
List<ThreePidMapping> results = new ArrayList<>();
|
||||
mappings.parallelStream().forEach(new Consumer<ThreePidMapping>() {
|
||||
@Override
|
||||
void accept(ThreePidMapping o) {
|
||||
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
|
||||
if (urOpt.isPresent()) {
|
||||
ThreePidMapping result = new ThreePidMapping();
|
||||
result.setMedium(o.getMedium())
|
||||
result.setValue(o.getValue())
|
||||
result.setMxid(getMxid(urOpt.get()))
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.config.ldap.LdapConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException
|
||||
import org.apache.directory.api.ldap.model.cursor.EntryCursor
|
||||
import org.apache.directory.api.ldap.model.entry.Attribute
|
||||
import org.apache.directory.api.ldap.model.entry.Entry
|
||||
import org.apache.directory.api.ldap.model.message.SearchScope
|
||||
import org.apache.directory.ldap.client.api.LdapConnection
|
||||
import org.apache.directory.ldap.client.api.LdapNetworkConnection
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class LdapProvider implements IThreePidProvider {
|
||||
|
||||
public static final String UID = "uid"
|
||||
public static final String MATRIX_ID = "mxid"
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(LdapProvider.class)
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg
|
||||
|
||||
@Autowired
|
||||
private LdapConfig ldapCfg
|
||||
|
||||
@Override
|
||||
boolean isEnabled() {
|
||||
return ldapCfg.isEnabled()
|
||||
}
|
||||
|
||||
private LdapConnection getConn() {
|
||||
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls())
|
||||
}
|
||||
|
||||
private void bind(LdapConnection conn) {
|
||||
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword())
|
||||
}
|
||||
|
||||
private String getUidAttribute() {
|
||||
return ldapCfg.getAttribute().getUid().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isLocal() {
|
||||
return true
|
||||
}
|
||||
|
||||
@Override
|
||||
int getPriority() {
|
||||
return 20
|
||||
}
|
||||
|
||||
Optional<String> lookup(LdapConnection conn, String medium, String value) {
|
||||
String uidAttribute = getUidAttribute()
|
||||
|
||||
Optional<String> queryOpt = ldapCfg.getIdentity().getQuery(medium)
|
||||
if (!queryOpt.isPresent()) {
|
||||
log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
String searchQuery = queryOpt.get().replaceAll("%3pid", value)
|
||||
EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
|
||||
try {
|
||||
while (cursor.next()) {
|
||||
Entry entry = cursor.get()
|
||||
log.info("Found possible match, DN: {}", entry.getDn().getName())
|
||||
|
||||
Attribute attribute = entry.get(uidAttribute)
|
||||
if (attribute == null) {
|
||||
log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute())
|
||||
continue
|
||||
}
|
||||
|
||||
String data = attribute.get().toString()
|
||||
if (data.length() < 1) {
|
||||
log.info("DN {}: empty attribute {}, skipping", ldapCfg.getAttribute())
|
||||
continue
|
||||
}
|
||||
|
||||
StringBuilder matrixId = new StringBuilder()
|
||||
// TODO Should we turn this block into a map of functions?
|
||||
String uidType = ldapCfg.getAttribute().getUid().getType()
|
||||
if (StringUtils.equals(UID, uidType)) {
|
||||
matrixId.append("@").append(data).append(":").append(srvCfg.getName())
|
||||
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
|
||||
matrixId.append(data)
|
||||
} else {
|
||||
log.warn("Bind was found but type {} is not supported", uidType)
|
||||
continue
|
||||
}
|
||||
|
||||
log.info("DN {} is a valid match", entry.getDn().getName())
|
||||
return Optional.of(matrixId.toString())
|
||||
}
|
||||
} catch (CursorLdapReferralException e) {
|
||||
log.warn("3PID {} is only available via referral, skipping", value)
|
||||
} finally {
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
|
||||
|
||||
LdapConnection conn = getConn()
|
||||
try {
|
||||
bind(conn)
|
||||
|
||||
Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid())
|
||||
if (mxid.isPresent()) {
|
||||
return Optional.of(new SingleLookupReply(request, mxid.get()));
|
||||
}
|
||||
} finally {
|
||||
conn.close()
|
||||
}
|
||||
|
||||
log.info("No match found")
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
log.info("Looking up {} mappings", mappings.size())
|
||||
List<ThreePidMapping> mappingsFound = new ArrayList<>()
|
||||
|
||||
LdapConnection conn = getConn()
|
||||
try {
|
||||
bind(conn)
|
||||
|
||||
for (ThreePidMapping mapping : mappings) {
|
||||
try {
|
||||
Optional<String> mxid = lookup(conn, mapping.getMedium(), mapping.getValue())
|
||||
if (mxid.isPresent()) {
|
||||
mapping.setMxid(mxid.get())
|
||||
mappingsFound.add(mapping)
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium())
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
conn.close()
|
||||
}
|
||||
|
||||
return mappingsFound
|
||||
}
|
||||
|
||||
}
|
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import groovy.json.JsonException
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.JsonSlurper
|
||||
import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
import org.apache.http.HttpEntity
|
||||
import org.apache.http.HttpResponse
|
||||
import org.apache.http.client.HttpClient
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
import org.apache.http.entity.ContentType
|
||||
import org.apache.http.impl.client.HttpClients
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.context.annotation.Scope
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
@Lazy
|
||||
public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher {
|
||||
|
||||
public static final String THREEPID_TEST_MEDIUM = "email"
|
||||
public static final String THREEPID_TEST_ADDRESS = "john.doe@example.org"
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(RemoteIdentityServerFetcher.class)
|
||||
|
||||
private JsonSlurper json = new JsonSlurper()
|
||||
|
||||
@Override
|
||||
boolean isUsable(String remote) {
|
||||
try {
|
||||
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
|
||||
"${remote}/_matrix/identity/api/v1/lookup?medium=${THREEPID_TEST_MEDIUM}&address=${THREEPID_TEST_ADDRESS}"
|
||||
).openConnection()
|
||||
// TODO turn this into a configuration property
|
||||
rootSrvConn.setConnectTimeout(2000)
|
||||
|
||||
if (rootSrvConn.getResponseCode() != 200) {
|
||||
return false
|
||||
}
|
||||
|
||||
def output = json.parseText(rootSrvConn.getInputStream().getText())
|
||||
if (output['address']) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (IOException | JsonException e) {
|
||||
log.info("{} is not a usable Identity Server: {}", remote, e.getMessage())
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(String remote, SingleLookupRequest request) {
|
||||
log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote)
|
||||
|
||||
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
|
||||
"${remote}/_matrix/identity/api/v1/lookup?medium=${request.getType()}&address=${request.getThreePid()}"
|
||||
).openConnection()
|
||||
|
||||
try {
|
||||
String outputRaw = rootSrvConn.getInputStream().getText()
|
||||
def output = json.parseText(outputRaw)
|
||||
if (output['address']) {
|
||||
log.info("Found 3PID mapping: {}", output)
|
||||
|
||||
return Optional.of(SingleLookupReply.fromRecursive(request, outputRaw))
|
||||
}
|
||||
|
||||
log.info("Empty 3PID mapping from {}", remote)
|
||||
return Optional.empty()
|
||||
} catch (IOException e) {
|
||||
log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage())
|
||||
return Optional.empty()
|
||||
} catch (JsonException e) {
|
||||
log.warn("Invalid JSON answer from {}", remote)
|
||||
return Optional.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings) {
|
||||
List<ThreePidMapping> mappingsFound = new ArrayList<>()
|
||||
|
||||
ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest()
|
||||
mappingRequest.setMappings(mappings)
|
||||
|
||||
String url = "${remote}/_matrix/identity/api/v1/bulk_lookup"
|
||||
HttpClient client = HttpClients.createDefault()
|
||||
try {
|
||||
HttpPost request = new HttpPost(url)
|
||||
request.setEntity(
|
||||
EntityBuilder.create()
|
||||
.setText(JsonOutput.toJson(mappingRequest))
|
||||
.setContentType(ContentType.APPLICATION_JSON)
|
||||
.build()
|
||||
)
|
||||
|
||||
HttpResponse response = client.execute(request)
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
log.info("Could not perform lookup at {} due to HTTP return code: {}", url, response.getStatusLine().getStatusCode())
|
||||
return mappingsFound
|
||||
}
|
||||
|
||||
HttpEntity entity = response.getEntity()
|
||||
if (entity != null) {
|
||||
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(entity.getContent().getText())
|
||||
for (List<String> mappingRaw : input.getThreepids()) {
|
||||
ThreePidMapping mapping = new ThreePidMapping()
|
||||
mapping.setMedium(mappingRaw.get(0))
|
||||
mapping.setValue(mappingRaw.get(1))
|
||||
mapping.setMxid(mappingRaw.get(2))
|
||||
mappingsFound.add(mapping)
|
||||
}
|
||||
} else {
|
||||
log.info("HTTP response from {} was empty", remote)
|
||||
}
|
||||
|
||||
return mappingsFound
|
||||
} finally {
|
||||
response.close()
|
||||
}
|
||||
} finally {
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.lookup.strategy
|
||||
|
||||
import edazdarevic.commons.net.CIDRUtils
|
||||
import io.kamax.mxisd.config.RecursiveLookupConfig
|
||||
import io.kamax.mxisd.lookup.*
|
||||
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
import java.util.function.Predicate
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@Component
|
||||
class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBean {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class)
|
||||
|
||||
@Autowired
|
||||
private RecursiveLookupConfig recursiveCfg
|
||||
|
||||
@Autowired
|
||||
private List<IThreePidProvider> providers
|
||||
|
||||
@Autowired
|
||||
private IBridgeFetcher bridge
|
||||
|
||||
private List<CIDRUtils> allowedCidr = new ArrayList<>()
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
log.info("Found ${providers.size()} providers")
|
||||
|
||||
providers.sort(new Comparator<IThreePidProvider>() {
|
||||
|
||||
@Override
|
||||
int compare(IThreePidProvider o1, IThreePidProvider o2) {
|
||||
return Integer.compare(o2.getPriority(), o1.getPriority())
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
log.info("Recursive lookup enabled: {}", recursiveCfg.isEnabled())
|
||||
for (String cidr : recursiveCfg.getAllowedCidr()) {
|
||||
log.info("{} is allowed for recursion", cidr)
|
||||
allowedCidr.add(new CIDRUtils(cidr))
|
||||
}
|
||||
}
|
||||
|
||||
boolean isAllowedForRecursive(String source) {
|
||||
boolean canRecurse = false
|
||||
|
||||
if (recursiveCfg.isEnabled()) {
|
||||
log.debug("Checking {} CIDRs for recursion", allowedCidr.size())
|
||||
for (CIDRUtils cidr : allowedCidr) {
|
||||
if (cidr.isInRange(source)) {
|
||||
log.debug("{} is in range {}, allowing recursion", source, cidr.getNetworkAddress())
|
||||
canRecurse = true
|
||||
break
|
||||
} else {
|
||||
log.debug("{} is not in range {}", source, cidr.getNetworkAddress())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canRecurse
|
||||
}
|
||||
|
||||
List<IThreePidProvider> listUsableProviders(ALookupRequest request) {
|
||||
return listUsableProviders(request, false);
|
||||
}
|
||||
|
||||
List<IThreePidProvider> listUsableProviders(ALookupRequest request, boolean forceRecursive) {
|
||||
List<IThreePidProvider> usableProviders = new ArrayList<>()
|
||||
|
||||
boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester())
|
||||
|
||||
log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse)
|
||||
for (IThreePidProvider provider : providers) {
|
||||
if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) {
|
||||
usableProviders.add(provider)
|
||||
}
|
||||
}
|
||||
|
||||
return usableProviders
|
||||
}
|
||||
|
||||
@Override
|
||||
List<IThreePidProvider> getLocalProviders() {
|
||||
return providers.stream().filter(new Predicate<IThreePidProvider>() {
|
||||
@Override
|
||||
boolean test(IThreePidProvider iThreePidProvider) {
|
||||
return iThreePidProvider.isEnabled() && iThreePidProvider.isLocal()
|
||||
}
|
||||
}).collect(Collectors.toList())
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
|
||||
SingleLookupRequest req = new SingleLookupRequest();
|
||||
req.setType(medium)
|
||||
req.setThreePid(address)
|
||||
req.setRequester("Internal")
|
||||
return find(req, recursive)
|
||||
}
|
||||
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request, boolean forceRecursive) {
|
||||
for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) {
|
||||
Optional<SingleLookupReply> lookupDataOpt = provider.find(request)
|
||||
if (lookupDataOpt.isPresent()) {
|
||||
return lookupDataOpt
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
recursiveCfg.getBridge() != null &&
|
||||
recursiveCfg.getBridge().getEnabled() &&
|
||||
(!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
|
||||
) {
|
||||
log.info("Using bridge failover for lookup")
|
||||
return bridge.find(request)
|
||||
}
|
||||
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
return find(request, false)
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request) {
|
||||
return find(request, true)
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> find(BulkLookupRequest request) {
|
||||
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings())
|
||||
List<ThreePidMapping> mapFoundAll = new ArrayList<>()
|
||||
|
||||
for (IThreePidProvider provider : listUsableProviders(request)) {
|
||||
if (mapToDo.isEmpty()) {
|
||||
log.info("No more mappings to lookup")
|
||||
break
|
||||
} else {
|
||||
log.info("{} mappings remaining overall", mapToDo.size())
|
||||
}
|
||||
|
||||
log.info("Using provider {} for remaining mappings", provider.getClass().getSimpleName())
|
||||
List<ThreePidMapping> mapFound = provider.populate(mapToDo)
|
||||
log.info("Provider {} returned {} mappings", provider.getClass().getSimpleName(), mapFound.size())
|
||||
mapFoundAll.addAll(mapFound)
|
||||
mapToDo.removeAll(mapFound)
|
||||
}
|
||||
|
||||
return mapFoundAll
|
||||
}
|
||||
|
||||
}
|
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.mapping;
|
||||
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
public class MappingManager {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(MappingManager.class);
|
||||
|
||||
private Map<String, Session> threePidLookups = new WeakHashMap<>();
|
||||
private Map<String, Session> sessions = new HashMap<>();
|
||||
private Timer cleaner;
|
||||
|
||||
MappingManager() {
|
||||
cleaner = new Timer();
|
||||
cleaner.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<Session> sList = new ArrayList<>(sessions.values());
|
||||
for (Session s : sList) {
|
||||
if (s.timestamp.plus(24, ChronoUnit.HOURS).isBefore(Instant.now())) { // TODO config timeout
|
||||
log.info("Session {} is obsolete, removing", s.sid);
|
||||
|
||||
sessions.remove(s.sid);
|
||||
threePidLookups.remove(s.hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 10 * 1000); // TODO config delay
|
||||
}
|
||||
|
||||
public String create(MappingSession data) {
|
||||
String sid;
|
||||
do {
|
||||
sid = Long.toString(System.currentTimeMillis());
|
||||
} while (sessions.containsKey(sid));
|
||||
|
||||
String threePidHash = data.getMedium() + data.getValue();
|
||||
Session session = threePidLookups.get(threePidHash);
|
||||
if (session != null) {
|
||||
sid = session.sid;
|
||||
} else {
|
||||
// TODO perform some kind of validation
|
||||
|
||||
session = new Session(sid, threePidHash, data);
|
||||
sessions.put(sid, session);
|
||||
threePidLookups.put(threePidHash, session);
|
||||
}
|
||||
|
||||
log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
|
||||
return sid;
|
||||
}
|
||||
|
||||
public void validate(String sid, String secret, String token) {
|
||||
Session s = sessions.get(sid);
|
||||
if (s == null || !StringUtils.equals(s.secret, secret)) {
|
||||
throw new BadRequestException("sid or secret are not valid");
|
||||
}
|
||||
|
||||
// TODO actually check token
|
||||
|
||||
s.isValidated = true;
|
||||
s.validationTimestamp = Instant.now();
|
||||
}
|
||||
|
||||
public Optional<ThreePidValidation> getValidated(String sid, String secret) {
|
||||
Session s = sessions.get(sid);
|
||||
if (s != null && StringUtils.equals(s.secret, secret)) {
|
||||
return Optional.of(new ThreePidValidation(s.medium, s.address, s.validationTimestamp));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void bind(String sid, String secret, String mxid) {
|
||||
Session s = sessions.get(sid);
|
||||
if (s == null || !StringUtils.equals(s.secret, secret)) {
|
||||
throw new BadRequestException("sid or secret are not valid");
|
||||
}
|
||||
|
||||
log.info("Performed bind for mxid {}", mxid);
|
||||
// TODO perform bind, whatever it is
|
||||
}
|
||||
|
||||
private class Session {
|
||||
|
||||
private String sid;
|
||||
private String hash;
|
||||
private Instant timestamp;
|
||||
private Instant validationTimestamp;
|
||||
private boolean isValidated;
|
||||
private String secret;
|
||||
private String medium;
|
||||
private String address;
|
||||
|
||||
public Session(String sid, String hash, MappingSession data) {
|
||||
this.sid = sid;
|
||||
this.hash = hash;
|
||||
timestamp = Instant.now();
|
||||
validationTimestamp = Instant.now();
|
||||
secret = data.getSecret();
|
||||
medium = data.getMedium();
|
||||
address = data.getValue();
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Instant timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Instant getValidationTimestamp() {
|
||||
return validationTimestamp;
|
||||
}
|
||||
|
||||
public void setValidationTimestamp(Instant validationTimestamp) {
|
||||
this.validationTimestamp = validationTimestamp;
|
||||
}
|
||||
|
||||
public boolean isValidated() {
|
||||
return isValidated;
|
||||
}
|
||||
|
||||
public void setValidated(boolean validated) {
|
||||
isValidated = validated;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public void setMedium(String medium) {
|
||||
this.medium = medium;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.signature
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import org.json.JSONObject
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
import java.security.MessageDigest
|
||||
|
||||
@Component
|
||||
class SignatureManager implements InitializingBean {
|
||||
|
||||
@Autowired
|
||||
private KeyManager keyMgr
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg
|
||||
|
||||
private EdDSAEngine signEngine
|
||||
|
||||
private String sign(String message) {
|
||||
byte[] signRaw = signEngine.signOneShot(message.getBytes())
|
||||
return Base64.getEncoder().encodeToString(signRaw)
|
||||
}
|
||||
|
||||
JSONObject signMessageJson(String message) {
|
||||
String sign = sign(message)
|
||||
|
||||
JSONObject keySignature = new JSONObject()
|
||||
keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign)
|
||||
JSONObject signature = new JSONObject()
|
||||
signature.put("${srvCfg.getName()}", keySignature)
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
JsonObject signMessageGson(String message) {
|
||||
String sign = sign(message)
|
||||
|
||||
JsonObject keySignature = new JsonObject()
|
||||
keySignature.addProperty("ed25519:${keyMgr.getCurrentIndex()}", sign)
|
||||
JsonObject signature = new JsonObject()
|
||||
signature.add("${srvCfg.getName()}", keySignature);
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm()))
|
||||
signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex()))
|
||||
}
|
||||
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite;
|
||||
|
||||
import com.j256.ormlite.dao.CloseableWrappedIterable;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class OrmLiteSqliteStorage implements IStorage {
|
||||
|
||||
private Dao<ThreePidInviteIO, String> invDao;
|
||||
|
||||
OrmLiteSqliteStorage(String path) {
|
||||
try {
|
||||
File parent = new File(path).getParentFile();
|
||||
if (!parent.mkdirs() && !parent.isDirectory()) {
|
||||
throw new RuntimeException("Unable to create DB parent directory: " + parent);
|
||||
}
|
||||
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
|
||||
invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class);
|
||||
TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThreePidInviteIO> getInvites() {
|
||||
try (CloseableWrappedIterable<ThreePidInviteIO> t = invDao.getWrappedIterable()) {
|
||||
List<ThreePidInviteIO> ioList = new ArrayList<>();
|
||||
t.forEach(ioList::add);
|
||||
return ioList;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertInvite(IThreePidInviteReply data) {
|
||||
try {
|
||||
int updated = invDao.create(new ThreePidInviteIO(data));
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteInvite(String id) {
|
||||
try {
|
||||
int updated = invDao.deleteById(id);
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite;
|
||||
|
||||
import io.kamax.mxisd.config.SQLiteStorageConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component
|
||||
public class OrmLiteSqliteStorageBeanFactory implements FactoryBean<IStorage> {
|
||||
|
||||
@Autowired
|
||||
private StorageConfig storagecfg;
|
||||
|
||||
@Autowired
|
||||
private SQLiteStorageConfig cfg;
|
||||
|
||||
private OrmLiteSqliteStorage storage;
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
if (StringUtils.equals("sqlite", storagecfg.getBackend())) {
|
||||
if (StringUtils.isBlank(cfg.getDatabase())) {
|
||||
throw new ConfigurationException("storage.provider.sqlite.database");
|
||||
}
|
||||
|
||||
storage = new OrmLiteSqliteStorage(cfg.getDatabase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStorage getObject() throws Exception {
|
||||
if (storage == null) {
|
||||
throw new FactoryBeanNotInitializedException();
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return OrmLiteSqliteStorage.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -1,27 +1,26 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* */
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package edazdarevic.commons.net;
|
||||
|
||||
@@ -37,6 +36,7 @@ import java.util.List;
|
||||
* both IPv4 and IPv6.
|
||||
*/
|
||||
public class CIDRUtils {
|
||||
|
||||
private final String cidr;
|
||||
|
||||
private InetAddress inetAddress;
|
||||
@@ -44,7 +44,6 @@ public class CIDRUtils {
|
||||
private InetAddress endAddress;
|
||||
private final int prefixLength;
|
||||
|
||||
|
||||
public CIDRUtils(String cidr) throws UnknownHostException {
|
||||
|
||||
this.cidr = cidr;
|
||||
@@ -66,7 +65,6 @@ public class CIDRUtils {
|
||||
|
||||
|
||||
private void calculate() throws UnknownHostException {
|
||||
|
||||
ByteBuffer maskBuffer;
|
||||
int targetSize;
|
||||
if (inetAddress.getAddress().length == 4) {
|
||||
@@ -120,14 +118,9 @@ public class CIDRUtils {
|
||||
}
|
||||
|
||||
public String getNetworkAddress() {
|
||||
|
||||
return this.startAddress.getHostAddress();
|
||||
}
|
||||
|
||||
public String getBroadcastAddress() {
|
||||
return this.endAddress.getHostAddress();
|
||||
}
|
||||
|
||||
public boolean isInRange(String ipAddress) throws UnknownHostException {
|
||||
InetAddress address = InetAddress.getByName(ipAddress);
|
||||
BigInteger start = new BigInteger(1, this.startAddress.getAddress());
|
||||
@@ -139,4 +132,5 @@ public class CIDRUtils {
|
||||
|
||||
return (st == -1 || st == 0) && (te == -1 || te == 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
33
src/main/java/io/kamax/matrix/MalformedEventException.java
Normal file
33
src/main/java/io/kamax/matrix/MalformedEventException.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
public class MalformedEventException extends MatrixException {
|
||||
|
||||
public MalformedEventException(String s) {
|
||||
super("M_NOT_JSON", s);
|
||||
}
|
||||
|
||||
public static MalformedEventException forId(String id) {
|
||||
return new MalformedEventException("Event " + id + " is malformed");
|
||||
}
|
||||
|
||||
}
|
44
src/main/java/io/kamax/matrix/MatrixErrorInfo.java
Normal file
44
src/main/java/io/kamax/matrix/MatrixErrorInfo.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
public class MatrixErrorInfo {
|
||||
|
||||
private String errcode;
|
||||
private String error;
|
||||
|
||||
public MatrixErrorInfo(String errcode) {
|
||||
this.errcode = errcode;
|
||||
}
|
||||
|
||||
public MatrixErrorInfo(Throwable t) {
|
||||
this.errcode = "AS_INTERNAL_SERVER_ERROR";
|
||||
}
|
||||
|
||||
public String getErrcode() {
|
||||
return errcode;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/io/kamax/matrix/MatrixException.java
Normal file
48
src/main/java/io/kamax/matrix/MatrixException.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
public class MatrixException extends RuntimeException {
|
||||
|
||||
private String errorCode;
|
||||
private String error;
|
||||
|
||||
public MatrixException(String errorCode, String error) {
|
||||
super(errorCode + ": " + error);
|
||||
this.errorCode = errorCode;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public MatrixException(String errorCode, String error, Throwable t) {
|
||||
super(errorCode + ": " + error, t);
|
||||
this.errorCode = errorCode;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
}
|
170
src/main/java/io/kamax/matrix/MatrixID.java
Normal file
170
src/main/java/io/kamax/matrix/MatrixID.java
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MatrixID implements _MatrixID {
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private MatrixID mxId;
|
||||
|
||||
public Builder(String id) {
|
||||
mxId = MatrixID.parse(id);
|
||||
}
|
||||
|
||||
public MatrixID valid() {
|
||||
if (!mxId.isValid()) {
|
||||
throw new IllegalArgumentException(mxId + " is not a valid Matrix ID");
|
||||
}
|
||||
return mxId;
|
||||
}
|
||||
|
||||
public MatrixID acceptable() {
|
||||
if (!mxId.isAcceptable()) {
|
||||
throw new IllegalArgumentException(mxId + " is not an acceptable Matrix ID");
|
||||
}
|
||||
return mxId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final String ALLOWED_CHARS = "0-9a-z-.=_/";
|
||||
public static final Pattern LAX_PATTERN = Pattern.compile("@(.*?):(.+)");
|
||||
public static final Pattern STRICT_PATTERN = Pattern.compile("@([" + ALLOWED_CHARS + "]+):(.+)");
|
||||
|
||||
private String id;
|
||||
|
||||
private String localpart;
|
||||
private String domain;
|
||||
|
||||
private static String buildRaw(String localpart, String domain) {
|
||||
return "@" + localpart + ":" + domain;
|
||||
}
|
||||
|
||||
private static MatrixID parse(String id) {
|
||||
Matcher m = LAX_PATTERN.matcher(id);
|
||||
if (!m.matches()) {
|
||||
throw new IllegalArgumentException(id + " is not a Matrix ID");
|
||||
}
|
||||
|
||||
MatrixID mxId = new MatrixID();
|
||||
mxId.id = id;
|
||||
mxId.localpart = m.group(1);
|
||||
mxId.domain = m.group(2);
|
||||
return mxId;
|
||||
}
|
||||
|
||||
public static Builder from(String id) {
|
||||
return new Builder(id);
|
||||
}
|
||||
|
||||
public static Builder from(String local, String domain) {
|
||||
return from(buildRaw(local, domain));
|
||||
}
|
||||
|
||||
public static MatrixID asValid(String id) {
|
||||
return new Builder(id).valid();
|
||||
}
|
||||
|
||||
public static MatrixID asAcceptable(String local, String domain) {
|
||||
return from(local, domain).acceptable();
|
||||
}
|
||||
|
||||
public static MatrixID asAcceptable(String id) {
|
||||
return from(id).acceptable();
|
||||
}
|
||||
|
||||
private MatrixID() {
|
||||
// not for public consumption
|
||||
}
|
||||
|
||||
private MatrixID(MatrixID mxId) {
|
||||
this.id = mxId.id;
|
||||
this.localpart = mxId.localpart;
|
||||
this.domain = mxId.domain;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public MatrixID(String mxId) {
|
||||
this(parse(mxId));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public MatrixID(String localpart, String domain) {
|
||||
this(parse(buildRaw(localpart, domain)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalPart() {
|
||||
return localpart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatrixID canonicalize() {
|
||||
return parse(getId().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return isAcceptable() && STRICT_PATTERN.matcher(id).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAcceptable() {
|
||||
// TODO properly implement
|
||||
|
||||
return id.length() <= 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof MatrixID)) return false;
|
||||
|
||||
MatrixID matrixID = (MatrixID) o;
|
||||
|
||||
return id.equals(matrixID.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getId();
|
||||
}
|
||||
|
||||
}
|
93
src/main/java/io/kamax/matrix/MatrixIdCodec.java
Normal file
93
src/main/java/io/kamax/matrix/MatrixIdCodec.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2018 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.kamax.matrix.MatrixID.ALLOWED_CHARS;
|
||||
|
||||
public class MatrixIdCodec {
|
||||
|
||||
public static final String DELIMITER = "=";
|
||||
public static final String ENCODE_REGEX = "[^" + ALLOWED_CHARS + "]+";
|
||||
public static final Pattern ENCODE_PATTERN = Pattern.compile(ENCODE_REGEX);
|
||||
public static final Pattern DECODE_PATTERN = Pattern.compile("(=[0-9a-f]{2})+");
|
||||
|
||||
private MatrixIdCodec() {
|
||||
// not for public consumption
|
||||
}
|
||||
|
||||
public static String encode(String decoded) {
|
||||
decoded = decoded.toLowerCase();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Character c : decoded.toCharArray()) {
|
||||
String s = c.toString();
|
||||
Matcher lp = ENCODE_PATTERN.matcher(s);
|
||||
if (!lp.find()) {
|
||||
builder.append(s);
|
||||
} else {
|
||||
for (byte b : c.toString().getBytes(StandardCharsets.UTF_8)) {
|
||||
builder.append(DELIMITER);
|
||||
builder.append(Hex.encodeHexString(new byte[] { b }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String decode(String encoded) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
Matcher m = DECODE_PATTERN.matcher(encoded);
|
||||
int prevEnd = 0;
|
||||
while (m.find()) {
|
||||
try {
|
||||
int start = m.start();
|
||||
int end = m.end();
|
||||
String sub = encoded.substring(start, end).replaceAll(DELIMITER, "");
|
||||
String decoded = new String(Hex.decodeHex(sub.toCharArray()), StandardCharsets.UTF_8);
|
||||
builder.append(encoded, prevEnd, start);
|
||||
builder.append(decoded);
|
||||
prevEnd = end - 1;
|
||||
} catch (DecoderException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
prevEnd++;
|
||||
if (prevEnd < encoded.length()) {
|
||||
builder.append(encoded, prevEnd, encoded.length());
|
||||
}
|
||||
|
||||
if (builder.length() == 0) {
|
||||
return encoded;
|
||||
} else {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
104
src/main/java/io/kamax/matrix/MatrixPath.java
Normal file
104
src/main/java/io/kamax/matrix/MatrixPath.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2018 Kamax Sàrl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class MatrixPath {
|
||||
|
||||
public static String encode(String element) {
|
||||
try {
|
||||
return URLEncoder.encode(element, StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// This would be madness if it happened, but we need to handle it still
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static MatrixPath with(String base) {
|
||||
return new MatrixPath().add(base);
|
||||
}
|
||||
|
||||
public static MatrixPath root() {
|
||||
return with("");
|
||||
}
|
||||
|
||||
public static MatrixPath base() {
|
||||
return root().add("_matrix");
|
||||
}
|
||||
|
||||
public static MatrixPath client() {
|
||||
return base().add("client");
|
||||
}
|
||||
|
||||
public static MatrixPath clientR0() {
|
||||
return client().add("v3");
|
||||
}
|
||||
|
||||
private StringBuilder path = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Add the raw element to this path
|
||||
*
|
||||
* @param element
|
||||
* The raw element to be added as is to the path, without encoding or path separator
|
||||
* @return The MatrixPath
|
||||
*/
|
||||
public MatrixPath put(String element) {
|
||||
path.append(element);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL encode and add a new path element
|
||||
*
|
||||
* This method handle path separators
|
||||
*
|
||||
* @param element
|
||||
* The element to be encoded and added.
|
||||
* @return The MatrixPath
|
||||
*/
|
||||
public MatrixPath add(String element) {
|
||||
// We add a path separator if this is the first character or if the last character is not a path separator
|
||||
// already
|
||||
if (path.length() == 0 || path.lastIndexOf("/", 0) < path.length() - 1) {
|
||||
put("/");
|
||||
}
|
||||
put(encode(element));
|
||||
return this;
|
||||
}
|
||||
|
||||
public String get() {
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return get();
|
||||
}
|
||||
|
||||
public URI toURI() {
|
||||
return URI.create(toString());
|
||||
}
|
||||
|
||||
}
|
61
src/main/java/io/kamax/matrix/ThreePid.java
Normal file
61
src/main/java/io/kamax/matrix/ThreePid.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
public class ThreePid implements _ThreePid {
|
||||
|
||||
private String medium;
|
||||
private String address;
|
||||
|
||||
public ThreePid(String medium, String address) {
|
||||
this.medium = medium;
|
||||
this.address = address.toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ThreePid threePid = (ThreePid) o;
|
||||
|
||||
if (!medium.equals(threePid.medium)) return false;
|
||||
return address.equals(threePid.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = medium.hashCode();
|
||||
result = 31 * result + address.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
61
src/main/java/io/kamax/matrix/ThreePidMapping.java
Normal file
61
src/main/java/io/kamax/matrix/ThreePidMapping.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* matrix-java-sdk - Matrix Client SDK for Java
|
||||
* Copyright (C) 2017 Kamax Sarl
|
||||
*
|
||||
* https://www.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.matrix;
|
||||
|
||||
public class ThreePidMapping implements _ThreePidMapping {
|
||||
|
||||
private _ThreePid threePid;
|
||||
private _MatrixID mxId;
|
||||
|
||||
public ThreePidMapping(_ThreePid threePid, _MatrixID mxId) {
|
||||
this.threePid = threePid;
|
||||
this.mxId = mxId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public _ThreePid getThreePid() {
|
||||
return threePid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public _MatrixID getMatrixId() {
|
||||
return mxId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ThreePidMapping that = (ThreePidMapping) o;
|
||||
|
||||
if (!threePid.equals(that.threePid)) return false;
|
||||
return mxId.equals(that.mxId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = threePid.hashCode();
|
||||
result = 31 * result + mxId.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user