mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-06-28 20:18:10 +03:00
Compare commits
567 commits
Author | SHA1 | Date | |
---|---|---|---|
|
e1ee1099cc | ||
|
b0e840a040 | ||
|
c76aae76a5 | ||
|
261e16cecb | ||
|
3c9e009433 | ||
|
2379fe5798 | ||
|
911ca1e69f | ||
|
90f74aaf25 | ||
|
880957fa9b | ||
|
2fea94071b | ||
|
b41e8db336 | ||
|
76570ddbe5 | ||
|
3a160b8124 | ||
|
8fb1dc275c | ||
|
95aed151e7 | ||
|
6934c37a33 | ||
|
5155c470c6 | ||
|
533dcfce14 | ||
|
d2f83d81eb | ||
|
2379398c30 | ||
|
f53c48a5ae | ||
|
394b64e22d | ||
|
7c15742478 | ||
|
d65fffade9 | ||
|
bb6c95ca12 | ||
|
9027987b29 | ||
|
d410533c8d | ||
|
af2c5c33e8 | ||
|
e67bb6a12f | ||
|
e2e0ef6ea4 | ||
|
18419fcd3a | ||
|
73804a555e | ||
|
b16281e04a | ||
|
06f0bc873a | ||
|
834fbab12b | ||
|
822872992f | ||
|
ac43034d45 | ||
|
00446b40f0 | ||
|
9e29053afa | ||
|
2e93102106 | ||
|
d4b69195b3 | ||
|
a67fbac8bc | ||
|
a7abb9cbfc | ||
|
689478ee0f | ||
|
df77a1ebd6 | ||
|
641796b44e | ||
|
b7ed56b27e | ||
|
f119bd4b30 | ||
|
bbff0eac64 | ||
|
41f93b9f8d | ||
|
263cf26258 | ||
|
b9ad7c0f26 | ||
|
5ba32e3405 | ||
|
907332ff06 | ||
|
1d63fbbd93 | ||
|
2f4667f5a6 | ||
|
92ada65079 | ||
|
29d98defff | ||
|
d7a379383b | ||
|
0111b2ca2b | ||
|
4671dfe49d | ||
|
bb63aaa0ab | ||
|
912caa6b8a | ||
|
926094076c | ||
|
a1af61a599 | ||
|
81b16cb54e | ||
|
a486f21fa2 | ||
|
86ea247f07 | ||
|
d26b179006 | ||
|
c4d1251429 | ||
|
f16f5fbc6d | ||
|
abe904d73c | ||
|
eaf685897f | ||
|
c8934d887a | ||
|
070a5d9b69 | ||
|
cc2bce4300 | ||
|
c7f4d8ac49 | ||
|
7dcb08fdaf | ||
|
9d870849a1 | ||
|
dda3ebc7b4 | ||
|
537623afaf | ||
|
0cff6e247a | ||
|
b85075c559 | ||
|
3e654f4d79 | ||
|
46f1f7b69e | ||
|
981f2ac3dd | ||
|
8509cbb6b5 | ||
|
1119625d12 | ||
|
9a69426547 | ||
|
31cbfe2919 | ||
|
27ebadcd19 | ||
|
b3349044b5 | ||
|
c43edeb982 | ||
|
1ff58099bd | ||
|
9fba637f83 | ||
|
ca70ee78d1 | ||
|
5e116a81e5 | ||
|
b0f799d194 | ||
|
63f9f8e21d | ||
|
b8ccbc5e48 | ||
|
a687c5afd8 | ||
|
3969d81db7 | ||
|
a30d0624a1 | ||
|
d5abe0d411 | ||
|
1e7a856a99 | ||
|
e6f5b585a7 | ||
|
2ed4abf9b0 | ||
|
af2dcec8cd | ||
|
9bffe07d36 | ||
|
4be299f6ca | ||
|
ef4f14f9b4 | ||
|
d720328bc4 | ||
|
88f1eaf750 | ||
|
a5ef86b105 | ||
|
b1a5ecdc13 | ||
|
68e9affbe0 | ||
|
7d7485afdc | ||
|
c2926b5b40 | ||
|
9c82d76781 | ||
|
450774de7e | ||
|
f88c0308f8 | ||
|
20f713be05 | ||
|
5bf92d9a00 | ||
|
903c4d40c5 | ||
|
ede9ab2c85 | ||
|
c8c83c0dba | ||
|
1bd5d8854c | ||
|
992d31c883 | ||
|
accbbe6b13 | ||
|
0894e0b9c3 | ||
|
925007015f | ||
|
ec526a343d | ||
|
1bebd8de2c | ||
|
c7781b30be | ||
|
df9d05a49c | ||
|
03d53d4a09 | ||
|
c261496af8 | ||
|
8c11ab0cbe | ||
|
78b4f1e3aa | ||
|
bbc5f1722b | ||
|
51411c5838 | ||
|
3b22b76278 | ||
|
ef9cbfe0da | ||
|
353b663e12 | ||
|
78be606029 | ||
|
584acdb8c3 | ||
|
7f6a645dd7 | ||
|
c6930ded74 | ||
|
c2a6a408c4 | ||
|
01cd50840a | ||
|
bb4d5b99c6 | ||
|
ff2f647b50 | ||
|
79fc42e86a | ||
|
5b8aa8cd5e | ||
|
a4e5ef9d01 | ||
|
1d5044c24a | ||
|
b84911d445 | ||
|
6a173b9b1b | ||
|
6a53891c6a | ||
|
7c637e078d | ||
|
c9a81da60c | ||
|
6c0ead015b | ||
|
b5457ee866 | ||
|
52c9196dcc | ||
|
095a5aef8b | ||
|
765f1a9d8f | ||
|
9bd65c797b | ||
|
8908710ad6 | ||
|
748612783c | ||
|
42cf9bc79e | ||
|
8a81989d65 | ||
|
58fd3a7e8b | ||
|
cc6ed82afb | ||
|
00ab20473c | ||
|
cdb54b34de | ||
|
1ebe68f5b8 | ||
|
b719255bd5 | ||
|
6ecf716fca | ||
|
873100cf0a | ||
|
a8b165f081 | ||
|
06ada30459 | ||
|
6017a89e20 | ||
|
bd677c26ba | ||
|
e5840243b3 | ||
|
09d36e066a | ||
|
038af764a1 | ||
|
f7decac23d | ||
|
1710eb7bec | ||
|
3926f3e5bf | ||
|
7759ea9182 | ||
|
ac6c312ed4 | ||
|
3e6af5afd3 | ||
|
044813cca2 | ||
|
730efae7c7 | ||
|
bc6da641d6 | ||
|
5c7f7eedec | ||
|
e72e4ebb92 | ||
|
d811a04cba | ||
|
572052163b | ||
|
666c8a4b3e | ||
|
ef5f932afb | ||
|
3002371fad | ||
|
998db80837 | ||
|
494b3227b6 | ||
|
d686d9a388 | ||
|
e28c9773fc | ||
|
7ff062f9e4 | ||
|
29619bb7a4 | ||
|
3179ee00eb | ||
|
f484f045ca | ||
|
ead4689bcf | ||
|
7a96e67517 | ||
|
b21082e201 | ||
|
8cd43b0324 | ||
|
3e8c1adebe | ||
|
666644c9e0 | ||
|
3152758d31 | ||
|
80176ff1e1 | ||
|
11382d3465 | ||
|
31285a8066 | ||
|
3ec79e3e93 | ||
|
a4bf033aa8 | ||
|
4e50cea93a | ||
|
d40dc09aca | ||
|
0b59d6c0ed | ||
|
80fc2900c8 | ||
|
4f47398211 | ||
|
9676e55bcb | ||
|
aa7b007616 | ||
|
35bdf1607f | ||
|
7060697bad | ||
|
34ac6a0f28 | ||
|
5370130c2d | ||
|
db6ab061de | ||
|
aeb55470ce | ||
|
a0788e4623 | ||
|
0c754ae5e6 | ||
|
c9ccf36252 | ||
|
b7b7afbdbb | ||
|
2fdd7d0199 | ||
|
1e3676778e | ||
|
ab884c8d23 | ||
|
48946d6e74 | ||
|
f075f39954 | ||
|
f1922c52e2 | ||
|
f42e6de0b0 | ||
|
efe967587c | ||
|
f2b92c2bbd | ||
|
4251725467 | ||
|
82bf2fdf56 | ||
|
f1bc0ea28a | ||
|
8762aa470c | ||
|
cfcd0010a7 | ||
|
b3eb0ebb98 | ||
|
8e82f5cd84 | ||
|
90f6d002d1 | ||
|
449798d52b | ||
|
f321b8bd27 | ||
|
64635cbb9b | ||
|
4607ab88bf | ||
|
e6516a8991 | ||
|
dfbb6e507a | ||
|
0855fc589d | ||
|
c9b6b0279a | ||
|
f8b060422e | ||
|
31489a2b24 | ||
|
6c0500f528 | ||
|
5896a12449 | ||
|
240e36aab6 | ||
|
f6f6ea13ad | ||
|
967b81cc85 | ||
|
6dadea1b67 | ||
|
a601a4ceef | ||
|
1c90681b3b | ||
|
7b1f449667 | ||
|
ae24fd6ccb | ||
|
2ff1d81076 | ||
|
ee0a7bc25a | ||
|
ebbd1c87e8 | ||
|
3754a327b0 | ||
|
d2f34ced28 | ||
|
43626bf1f4 | ||
|
380179faa3 | ||
|
2e60d45c63 | ||
|
d678daac7b | ||
|
1bc9351b0c | ||
|
54bfc6de9c | ||
|
fe374c1f9e | ||
|
3f4bdceb5a | ||
|
3d61635c6b | ||
|
0c2779f1c0 | ||
|
8f598a40c5 | ||
|
4720e4d106 | ||
|
0482cfa9ab | ||
|
a2167d483a | ||
|
84a3845f1a | ||
|
0d5d772141 | ||
|
9df116f951 | ||
|
ff3d500cc0 | ||
|
2e10c78ad2 | ||
|
b6be2e243d | ||
|
44bc8b0bbc | ||
|
6cd5a69149 | ||
|
b638efc0d1 | ||
|
f2cbf0ed38 | ||
|
0f36dfec16 | ||
|
c1df548258 | ||
|
429c7a45c4 | ||
|
90e116720c | ||
|
4b222b9526 | ||
|
1df283d659 | ||
|
b45618c0da | ||
|
0241a4d887 | ||
|
de5ebe4a74 | ||
|
80862e6116 | ||
|
f22aa36926 | ||
|
198ce95176 | ||
|
fca2ed2447 | ||
|
2792b5a008 | ||
|
a0335bd340 | ||
|
3c6b8322a5 | ||
|
a83f225933 | ||
|
99af83fb06 | ||
|
c4672387ac | ||
|
fe7ae41f65 | ||
|
474d557e3f | ||
|
50e6bb3b49 | ||
|
e85a12afab | ||
|
9e83e8bec8 | ||
|
fce8453bd1 | ||
|
224649aa13 | ||
|
a3bcfed793 | ||
|
e1429356df | ||
|
75f51c7727 | ||
|
90162a1a25 | ||
|
62e9276481 | ||
|
79f933646c | ||
|
01c963d852 | ||
|
941bf115b1 | ||
|
2e6d7bd94a | ||
|
d262b99be6 | ||
|
b5e10e8f9d | ||
|
cebeb55c00 | ||
|
7e16f36ea0 | ||
|
8973a3462b | ||
|
82dc299752 | ||
|
19f966062b | ||
|
93f916192e | ||
|
dbeca56b05 | ||
|
d7474255da | ||
|
b72fb643d4 | ||
|
52e8f693b5 | ||
|
1f3b7e0552 | ||
|
183cfe949f | ||
|
aff254a875 | ||
|
e12f2ef897 | ||
|
58d8ba2358 | ||
|
1cc3edd15b | ||
|
606df6cb1a | ||
|
98a314f697 | ||
|
d03c6120ff | ||
|
a34267e902 | ||
|
0ccef10a93 | ||
|
29aee9dd30 | ||
|
1d563249d1 | ||
|
d0d2860317 | ||
|
faa5189795 | ||
|
d9082f21a3 | ||
|
3475f2f912 | ||
|
b635447eaf | ||
|
0fb6102c29 | ||
|
d4cc28f96a | ||
|
49a5215783 | ||
|
f12d13ef58 | ||
|
55c77dd343 | ||
|
caebd6b5de | ||
|
647c8dba5c | ||
|
6ffbc5515f | ||
|
65c6520001 | ||
|
ac9a78cea4 | ||
|
00a4a13536 | ||
|
6e45a84c1c | ||
|
b31dd78b2b | ||
|
9de81095b1 | ||
|
4d1fd23e84 | ||
|
41f00c2310 | ||
|
85986c2916 | ||
|
1bc0443dd5 | ||
|
50de0b1e44 | ||
|
39d5eee51c | ||
|
8153c3a438 | ||
|
9d81db25d8 | ||
|
d4ca612bff | ||
|
680244e5d1 | ||
|
2f7b94365a | ||
|
60f742b3ef | ||
|
7fee478552 | ||
|
7efe7c8611 | ||
|
d4abf27989 | ||
|
e887035920 | ||
|
890591d2d2 | ||
|
82938fe8d4 | ||
|
cebe47939a | ||
|
fd24ca0ca7 | ||
|
663685934b | ||
|
43d944bee1 | ||
|
3a82065889 | ||
|
55c0cdfa0d | ||
|
b12c43676b | ||
|
9d49eebffe | ||
|
2aa8dffcaa | ||
|
216928f258 | ||
|
ec3775286c | ||
|
7950eea975 | ||
|
04dd7d655c | ||
|
bd83e8a4c5 | ||
|
d4f63a4e19 | ||
|
c458283efb | ||
|
bea7898939 | ||
|
bddf31c94e | ||
|
24d625fd16 | ||
|
0c23b59749 | ||
|
9b2c98e10b | ||
|
4a538cde61 | ||
|
d9be4bb577 | ||
|
ae426b2fd0 | ||
|
498325f3e8 | ||
|
907e7cc47e | ||
|
d4eabbc4c0 | ||
|
540ad0b0da | ||
|
3b8c01835d | ||
|
00baf4adf0 | ||
|
e338bb9a02 | ||
|
373fc8a255 | ||
|
73d8a037d5 | ||
|
aa47fd6f1b | ||
|
dec86c9a91 | ||
|
74af58bc7a | ||
|
d97b856ad6 | ||
|
5436b2a2d6 | ||
|
5aa4fe8d47 | ||
|
b10535042f | ||
|
9351f3ca1e | ||
|
e05aa4b204 | ||
|
bfa6966ec6 | ||
|
50b463b439 | ||
|
6caa34e255 | ||
|
26c017a277 | ||
|
48799cf3c2 | ||
|
3bc8040352 | ||
|
dcaec54814 | ||
|
76f8b4602c | ||
|
fc7f96d536 | ||
|
4ed687087f | ||
|
95da394a5d | ||
|
06e9bc8578 | ||
|
e0b3f3d6a5 | ||
|
6a057514b2 | ||
|
70d102222b | ||
|
55d2fbd57f | ||
|
111d7616d0 | ||
|
fb2883d215 | ||
|
dc664c7ee2 | ||
|
eff739ce12 | ||
|
17f9c28f3d | ||
|
71739f5670 | ||
|
e4bf8f1e9c | ||
|
a796b82a16 | ||
|
cf802fb0b3 | ||
|
57868a7136 | ||
|
8ac9866258 | ||
|
47392ceec7 | ||
|
c6f8793031 | ||
|
0d74d8a671 | ||
|
70012a2a8f | ||
|
3821fa7e51 | ||
|
930a5caf74 | ||
|
ffad29f53b | ||
|
bd4e454be9 | ||
|
0818b3037c | ||
|
43ffacdf5e | ||
|
5c374462ef | ||
|
ef5695f679 | ||
|
b4331819cb | ||
|
ebf25a65f7 | ||
|
5588b4aac1 | ||
|
fbaf9ab87f | ||
|
6d1440207b | ||
|
9d719c48e3 | ||
|
b0fba84fbb | ||
|
1ffd36fc82 | ||
|
9c359747ea | ||
|
2c1972c12c | ||
|
d30f0b900b | ||
|
f71444b8d6 | ||
|
a0ac58f0b5 | ||
|
a27d7f1597 | ||
|
7aa08c1846 | ||
|
2045f1ac99 | ||
|
292e7d2af7 | ||
|
5f7808afff | ||
|
3c10a668de | ||
|
ec222aed6f | ||
|
7dcc5aef3f | ||
|
ae994ebb4f | ||
|
553cdf5250 | ||
|
0d1b32fc1c | ||
|
7f4fe566de | ||
|
cc825df41e | ||
|
dd5ce00c3f | ||
|
7c7952545e | ||
|
cc8250d6cf | ||
|
3c5e25b67f | ||
|
a0722fb5f4 | ||
|
31b6ae35e7 | ||
|
8fb1349487 | ||
|
5631bc6af1 | ||
|
9cc1810831 | ||
|
841d01b417 | ||
|
a7d3cba949 | ||
|
b2d2059ebd | ||
|
10888ed2e7 | ||
|
82accb211d | ||
|
10df931c2a | ||
|
1996525b65 | ||
|
40d4681bce | ||
|
d11c9e92a5 | ||
|
60030bd769 | ||
|
bfa9a9c187 | ||
|
a560ab4812 | ||
|
a5b4282037 | ||
|
420fd53553 | ||
|
1bb7e99e12 | ||
|
c74f430129 | ||
|
df04b459be | ||
|
5a0ff3610b | ||
|
ca12e7cbd0 | ||
|
6fb9174681 | ||
|
36d97e7f8b | ||
|
28a9b5efc4 | ||
|
86f9f20b52 | ||
|
8bec134611 | ||
|
07be86f695 | ||
|
91f3773c54 | ||
|
b2486efb30 | ||
|
970761374a | ||
|
b43ff9e7a6 | ||
|
c7058499b7 | ||
|
6d5ae93889 | ||
|
78e5c8866f | ||
|
692aa8d3cf | ||
|
2324af1c46 | ||
|
7414132bad | ||
|
65ae13a042 | ||
|
230194f2a0 | ||
|
9bac9e3bef | ||
|
2d9037fedc | ||
|
385f2d9ec5 | ||
|
81b80a7938 | ||
|
bbb962c624 | ||
|
eb5bbb9acf | ||
|
3561522d14 | ||
|
c8768326ea | ||
|
dbdc1b4d6a | ||
|
d46c380f45 | ||
|
6bed8b383d | ||
|
857901a667 |
573 changed files with 11788 additions and 9999 deletions
86
.gitattributes
vendored
86
.gitattributes
vendored
|
@ -1,26 +1,78 @@
|
||||||
* text eol=lf
|
* text=auto eol=lf
|
||||||
*.bat text eol=crlf
|
*.[cC][mM][dD] text eol=crlf
|
||||||
*.sh text eol=lf
|
*.[bB][aA][tT] text eol=crlf
|
||||||
|
*.[pP][sS]1 text eol=crlf
|
||||||
|
*.[sS][hH] text eol=lf
|
||||||
|
|
||||||
*.patch text eol=lf
|
*.patch text eol=lf
|
||||||
*.java text eol=lf
|
|
||||||
*.scala text eol=lf
|
|
||||||
*.groovy text eol=lf
|
|
||||||
|
|
||||||
*.gradle text eol=crlf
|
|
||||||
gradle.properties text eol=crlf
|
|
||||||
/gradle/wrapper/gradle-wrapper.properties text eol=crlf
|
|
||||||
*.cfg text eol=lf
|
|
||||||
|
|
||||||
*.png binary
|
*.png binary
|
||||||
*.jar binary
|
|
||||||
*.war binary
|
|
||||||
*.lzma binary
|
*.lzma binary
|
||||||
*.zip binary
|
*.zip binary
|
||||||
*.gzip binary
|
*.gzip binary
|
||||||
*.dll binary
|
|
||||||
*.so binary
|
|
||||||
*.exe binary
|
*.exe binary
|
||||||
|
*.ico binary
|
||||||
|
*.eot binary
|
||||||
|
*.ttf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.a binary
|
||||||
|
*.lib binary
|
||||||
|
*.icns binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.mov binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.flv binary
|
||||||
|
*.fla binary
|
||||||
|
*.swf binary
|
||||||
|
*.gz binary
|
||||||
|
*.tar binary
|
||||||
|
*.tar.gz binary
|
||||||
|
*.7z binary
|
||||||
|
*.pyc binary
|
||||||
|
*.gpg binary
|
||||||
|
*.bin binary
|
||||||
|
|
||||||
*.gitattributes text eol=crlf
|
*.gitattributes text
|
||||||
*.gitignore text eol=crlf
|
.gitignore text
|
||||||
|
|
||||||
|
# Java sources
|
||||||
|
*.java text diff=java
|
||||||
|
*.kt text diff=kotlin
|
||||||
|
*.groovy text diff=java
|
||||||
|
*.scala text diff=java
|
||||||
|
*.gradle text diff=java
|
||||||
|
*.gradle.kts text diff=kotlin
|
||||||
|
|
||||||
|
# These files are text and should be normalized (Convert crlf => lf)
|
||||||
|
*.css text diff=css
|
||||||
|
*.scss text diff=css
|
||||||
|
*.sass text
|
||||||
|
*.df text
|
||||||
|
*.htm text diff=html
|
||||||
|
*.html text diff=html
|
||||||
|
*.js text
|
||||||
|
*.jsp text
|
||||||
|
*.jspf text
|
||||||
|
*.jspx text
|
||||||
|
*.properties text
|
||||||
|
*.tld text
|
||||||
|
*.tag text
|
||||||
|
*.tagx text
|
||||||
|
*.xml text
|
||||||
|
|
||||||
|
# These files are binary and should be left untouched
|
||||||
|
# (binary is a macro for -text -diff)
|
||||||
|
*.class binary
|
||||||
|
*.dll binary
|
||||||
|
*.ear binary
|
||||||
|
*.jar binary
|
||||||
|
*.so binary
|
||||||
|
*.war binary
|
||||||
|
*.jks binary
|
||||||
|
|
||||||
|
mvnw text eol=lf
|
||||||
|
gradlew text eol=lf
|
22
.github/workflows/push.yml
vendored
22
.github/workflows/push.yml
vendored
|
@ -6,20 +6,21 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.gradle/caches
|
path: ~/.gradle/caches
|
||||||
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
|
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 21
|
||||||
|
distribution: temurin
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
@ -27,20 +28,27 @@ jobs:
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
|
|
||||||
|
- name: Generate and submit dependency graph
|
||||||
|
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||||
|
|
||||||
- name: Create artifacts
|
- name: Create artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p artifacts/modules
|
mkdir -p artifacts/modules
|
||||||
cd LaunchServer/build/libs
|
cd LaunchServer/build/libs
|
||||||
|
mv proguard proguard-libraries
|
||||||
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
|
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
|
||||||
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
|
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
|
||||||
cd ../../..
|
cd ../../..
|
||||||
cp ServerWrapper/build/libs/ServerWrapper.jar artifacts/ServerWrapper.jar
|
cp ServerWrapper/build/libs/ServerWrapper.jar artifacts/ServerWrapper.jar
|
||||||
|
cp ServerWrapper/build/libs/ServerWrapper-inline.jar artifacts/ServerWrapperInline.jar
|
||||||
cp LauncherAuthlib/build/libs/LauncherAuthlib.jar artifacts/LauncherAuthlib.jar || true
|
cp LauncherAuthlib/build/libs/LauncherAuthlib.jar artifacts/LauncherAuthlib.jar || true
|
||||||
cp modules/*_module/build/libs/*.jar artifacts/modules || true
|
cp modules/*_module/build/libs/*.jar artifacts/modules || true
|
||||||
cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true
|
cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true
|
||||||
|
cp javaargs.txt artifacts/javaargs.txt || true
|
||||||
|
cp java24args.txt artifacts/java24args.txt || true
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Launcher
|
name: Launcher
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
@ -61,7 +69,7 @@ jobs:
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.event.ref, 'refs/tags')
|
if: startsWith(github.event.ref, 'refs/tags')
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
**Основные правила:**
|
**Основные правила:**
|
||||||
|
|
||||||
1. Все коммиты должны быть на русском языке.
|
1. Все коммиты должны быть на английском языке.
|
||||||
2. Запрещено использовать прошедшее время.
|
2. Запрещено использовать прошедшее время.
|
||||||
3. Обязательно должен быть использован префикс.
|
3. Обязательно должен быть использован префикс.
|
||||||
4. В конце не должно быть лишнего знака препинания.
|
4. В конце не должно быть лишнего знака препинания.
|
||||||
|
@ -38,10 +38,10 @@
|
||||||
|
|
||||||
| Префикс | Значение | Пример |
|
| Префикс | Значение | Пример |
|
||||||
| ------- | -------- | ------ |
|
| ------- | -------- | ------ |
|
||||||
| **[FIX]** | Всё, что касается исправления багов | [FIX] Баг с неудачной авторизацией |
|
| **[FIX]** | Всё, что касается исправления багов | [FIX] Bug with failed authorization |
|
||||||
| **[DOCS]** | Всё, что касается документации | [DOCS] Документирование API авторизации |
|
| **[DOCS]** | Всё, что касается документации | [DOCS] Documenting Authorization API |
|
||||||
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA при авторизации |
|
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA on authorization |
|
||||||
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Опечатки в модуле авторизации |
|
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Typos in the authorization module |
|
||||||
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Переход на EDA в модуле авторизации |
|
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Switching to EDA in the authorization module |
|
||||||
| **[TEST]** | Всё, что касается тестирования | [TEST] Покрытие модуля авторизации тестами |
|
| **[TEST]** | Всё, что касается тестирования | [TEST] Coverage of the authorization module with tests |
|
||||||
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Подключение Travis CI |
|
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Connecting Travis CI |
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
def mainClassName = "pro.gravit.launchserver.LaunchServerStarter"
|
def mainClassName = "pro.gravit.launchserver.Main"
|
||||||
def mainAgentName = "pro.gravit.launchserver.StarterAgent"
|
def mainAgentName = "pro.gravit.launchserver.StarterAgent"
|
||||||
|
|
||||||
evaluationDependsOn(':Launcher')
|
evaluationDependsOn(':Launcher')
|
||||||
|
@ -13,33 +13,37 @@
|
||||||
maven {
|
maven {
|
||||||
url "https://jitpack.io/"
|
url "https://jitpack.io/"
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url 'https://maven.gravit-support.ru/repository/jitpack'
|
||||||
|
credentials {
|
||||||
|
username = 'gravitlauncher'
|
||||||
|
password = 'gravitlauncher'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '21'
|
||||||
targetCompatibility = '17'
|
targetCompatibility = '21'
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
compileOnlyA
|
|
||||||
bundleOnly
|
bundleOnly
|
||||||
bundle
|
bundle
|
||||||
hikari
|
|
||||||
pack
|
pack
|
||||||
launch4j
|
proguardPack
|
||||||
bundleOnly.extendsFrom bundle
|
bundleOnly.extendsFrom bundle
|
||||||
api.extendsFrom bundle, hikari, pack, launch4j
|
api.extendsFrom bundle, pack
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
dependsOn parent.childProjects.Launcher.tasks.assemble
|
dependsOn parent.childProjects.Launcher.tasks.assemble
|
||||||
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } }
|
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||||
|
exclude("module-info.class")
|
||||||
from(parent.childProjects.Launcher.tasks.shadowJar)
|
from(parent.childProjects.Launcher.tasks.shadowJar)
|
||||||
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
|
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
|
||||||
manifest.attributes("Main-Class": mainClassName,
|
manifest.attributes("Main-Class": mainClassName,
|
||||||
"Premain-Class": mainAgentName,
|
"Premain-Class": mainAgentName,
|
||||||
"Multi-Release": "true",
|
"Multi-Release": "true",
|
||||||
"Can-Redefine-Classes": "true",
|
"Automatic-Module-Name": "launchserver"
|
||||||
"Can-Retransform-Classes": "true",
|
|
||||||
"Can-Set-Native-Method-Prefix": "true"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,128 +54,102 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task sourcesJar(type: Jar) {
|
tasks.register('sourcesJar', Jar) {
|
||||||
from sourceSets.main.allJava
|
from sourceSets.main.allJava
|
||||||
archiveClassifier.set('sources')
|
archiveClassifier.set('sources')
|
||||||
}
|
}
|
||||||
|
|
||||||
task javadocJar(type: Jar) {
|
tasks.register('javadocJar', Jar) {
|
||||||
from javadoc
|
from javadoc
|
||||||
archiveClassifier.set('javadoc')
|
archiveClassifier.set('javadoc')
|
||||||
}
|
}
|
||||||
|
|
||||||
task cleanjar(type: Jar, dependsOn: jar) {
|
tasks.register('cleanjar', Jar) {
|
||||||
|
dependsOn jar
|
||||||
archiveClassifier.set('clean')
|
archiveClassifier.set('clean')
|
||||||
manifest.attributes("Main-Class": mainClassName,
|
manifest.attributes("Main-Class": mainClassName,
|
||||||
"Premain-Class": mainAgentName,
|
"Automatic-Module-Name": "launchserver"
|
||||||
"Can-Redefine-Classes": "true",
|
|
||||||
"Can-Retransform-Classes": "true",
|
|
||||||
"Can-Set-Native-Method-Prefix": "true"
|
|
||||||
)
|
)
|
||||||
from sourceSets.main.output
|
from sourceSets.main.output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
pack project(':LauncherAPI')
|
pack(project(':LauncherAPI')) {
|
||||||
bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
|
exclude group: "com.google.code.gson"
|
||||||
bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
|
}
|
||||||
|
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
|
||||||
|
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1'
|
||||||
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
||||||
bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline-native', version: rootProject['verJline']
|
||||||
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
|
||||||
bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline-terminal-ffm', version: rootProject['verJline']
|
||||||
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: rootProject['verBcpkix']
|
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix']
|
||||||
|
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix']
|
||||||
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
|
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
|
||||||
bundle group: 'io.netty', name: 'netty-all', version: rootProject['verNetty']
|
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
|
||||||
|
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty']
|
||||||
|
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64'
|
||||||
|
//bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
|
||||||
|
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
|
||||||
|
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
|
||||||
|
//bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
|
||||||
|
// Netty
|
||||||
|
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
|
||||||
|
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
|
||||||
|
//
|
||||||
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
|
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
|
||||||
bundle group: 'mysql', name: 'mysql-connector-java', version: rootProject['verMySQLConn']
|
bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn']
|
||||||
|
bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn']
|
||||||
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
|
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
|
||||||
bundle group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
|
bundle group: 'com.h2database', name: 'h2', version: rootProject['verH2Conn']
|
||||||
|
proguardPack group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
|
||||||
bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j']
|
bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j']
|
||||||
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: rootProject['verLog4j']
|
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: rootProject['verLog4j']
|
||||||
bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt']
|
bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt']
|
||||||
bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt']
|
bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt']
|
||||||
bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt']
|
bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt']
|
||||||
|
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
|
||||||
|
annotationProcessor(group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'])
|
||||||
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
|
||||||
|
|
||||||
hikari 'io.micrometer:micrometer-core:1.8.4'
|
bundle 'io.micrometer:micrometer-core:1.14.4'
|
||||||
hikari('com.zaxxer:HikariCP:5.0.1') {
|
bundle('com.zaxxer:HikariCP:6.2.1') {
|
||||||
exclude group: 'javassist'
|
exclude group: 'javassist'
|
||||||
exclude group: 'io.micrometer'
|
exclude group: 'io.micrometer'
|
||||||
exclude group: 'org.slf4j'
|
exclude group: 'org.slf4j'
|
||||||
}
|
}
|
||||||
|
|
||||||
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j']) {
|
|
||||||
exclude group: 'org.apache.ant'
|
|
||||||
exclude group: 'net.java.abeille'
|
|
||||||
exclude group: 'foxtrot'
|
|
||||||
exclude group: 'com.jgoodies'
|
|
||||||
exclude group: 'org.slf4j'
|
|
||||||
}
|
|
||||||
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-win32') { transitive = false }
|
|
||||||
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-linux64') { transitive = false }
|
|
||||||
|
|
||||||
compileOnlyA group: 'com.google.guava', name: 'guava', version: rootProject['verGuavaC']
|
|
||||||
// Do not update (laggy deps).
|
|
||||||
compileOnlyA 'log4j:log4j:1.2.17'
|
|
||||||
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.14.1'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task hikari(type: Copy) {
|
tasks.register('dumpLibs', Copy) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
into "$buildDir/libs/libraries/hikaricp"
|
|
||||||
from configurations.hikari
|
|
||||||
}
|
|
||||||
|
|
||||||
task launch4j(type: Copy) {
|
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
|
||||||
into "$buildDir/libs/libraries/launch4j"
|
|
||||||
from(configurations.launch4j.collect {
|
|
||||||
it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it)
|
|
||||||
})
|
|
||||||
includeEmptyDirs false
|
|
||||||
eachFile { FileCopyDetails fcp ->
|
|
||||||
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
|
|
||||||
fcp.relativePath.pathString.contains("workdir")) {
|
|
||||||
def segments = fcp.relativePath.segments
|
|
||||||
def pathSegments = segments[1..-1] as String[]
|
|
||||||
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
|
|
||||||
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
|
|
||||||
fcp.mode = 0755
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task dumpLibs(type: Copy) {
|
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
|
||||||
dependsOn tasks.hikari, tasks.launch4j
|
|
||||||
into "$buildDir/libs/libraries"
|
into "$buildDir/libs/libraries"
|
||||||
from configurations.bundleOnly
|
from configurations.bundleOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
task dumpCompileOnlyLibs(type: Copy) {
|
tasks.register('dumpProguard', Copy) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
into "$buildDir/libs/launcher-libraries-compile"
|
into "$buildDir/libs/proguard"
|
||||||
from configurations.compileOnlyA
|
from configurations.proguardPack
|
||||||
}
|
}
|
||||||
|
|
||||||
task bundle(type: Zip) {
|
tasks.register('bundle', Zip) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
|
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar
|
||||||
archiveFileName = 'LaunchServer.zip'
|
archiveFileName = 'LaunchServer.zip'
|
||||||
destinationDirectory = file("$buildDir")
|
destinationDirectory = file("$buildDir")
|
||||||
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
|
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
|
||||||
from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
|
|
||||||
from(tasks.jar)
|
from(tasks.jar)
|
||||||
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
|
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
|
||||||
}
|
}
|
||||||
|
|
||||||
task dumpClientLibs(type: Copy) {
|
tasks.register('dumpClientLibs', Copy) {
|
||||||
dependsOn parent.childProjects.Launcher.tasks.build
|
dependsOn parent.childProjects.Launcher.tasks.build
|
||||||
into "$buildDir/libs/launcher-libraries"
|
into "$buildDir/libs/launcher-libraries"
|
||||||
from parent.childProjects.Launcher.tasks.dumpLibs
|
from parent.childProjects.Launcher.tasks.dumpLibs
|
||||||
}
|
}
|
||||||
|
|
||||||
assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
|
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar, tasks.dumpProguard
|
||||||
|
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -186,7 +164,7 @@ task dumpClientLibs(type: Copy) {
|
||||||
pom {
|
pom {
|
||||||
name = 'GravitLauncher LaunchServer API'
|
name = 'GravitLauncher LaunchServer API'
|
||||||
description = 'GravitLauncher LaunchServer Module API'
|
description = 'GravitLauncher LaunchServer Module API'
|
||||||
url = 'https://launcher.gravit.pro'
|
url = 'https://gravitlauncher.com'
|
||||||
licenses {
|
licenses {
|
||||||
license {
|
license {
|
||||||
name = 'GNU General Public License, Version 3.0'
|
name = 'GNU General Public License, Version 3.0'
|
||||||
|
@ -209,7 +187,7 @@ task dumpClientLibs(type: Copy) {
|
||||||
scm {
|
scm {
|
||||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||||
url = 'https://launcher.gravit.pro/'
|
url = 'https://gravitlauncher.com/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pro.gravit.launchserver;
|
package pro.gravit.launchserver;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launchserver.helper.HttpHelper;
|
import pro.gravit.launchserver.helper.HttpHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -17,26 +17,11 @@ public class HttpRequester {
|
||||||
public HttpRequester() {
|
public HttpRequester() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
|
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
|
||||||
private final Type type;
|
return new SimpleErrorHandler<>(clazz);
|
||||||
|
|
||||||
private SimpleErrorHandler(Type type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
|
|
||||||
if(statusCode < 200 || statusCode >= 300) {
|
|
||||||
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
|
|
||||||
}
|
|
||||||
if(type == Void.class) {
|
|
||||||
return new HttpHelper.HttpOptional<>(null, null, statusCode);
|
|
||||||
}
|
|
||||||
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
|
public <T> SimpleErrorHandler<T> makeEH(Type clazz) {
|
||||||
return new SimpleErrorHandler<>(clazz);
|
return new SimpleErrorHandler<>(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +33,7 @@ public <T> HttpRequest get(String url, String token) {
|
||||||
.header("Content-Type", "application/json; charset=UTF-8")
|
.header("Content-Type", "application/json; charset=UTF-8")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.timeout(Duration.ofMillis(10000));
|
.timeout(Duration.ofMillis(10000));
|
||||||
if(token != null) {
|
if (token != null) {
|
||||||
requestBuilder.header("Authorization", "Bearer ".concat(token));
|
requestBuilder.header("Authorization", "Bearer ".concat(token));
|
||||||
}
|
}
|
||||||
return requestBuilder.build();
|
return requestBuilder.build();
|
||||||
|
@ -65,7 +50,7 @@ public <T> HttpRequest post(String url, T request, String token) {
|
||||||
.header("Content-Type", "application/json; charset=UTF-8")
|
.header("Content-Type", "application/json; charset=UTF-8")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.timeout(Duration.ofMillis(10000));
|
.timeout(Duration.ofMillis(10000));
|
||||||
if(token != null) {
|
if (token != null) {
|
||||||
requestBuilder.header("Authorization", "Bearer ".concat(token));
|
requestBuilder.header("Authorization", "Bearer ".concat(token));
|
||||||
}
|
}
|
||||||
return requestBuilder.build();
|
return requestBuilder.build();
|
||||||
|
@ -78,6 +63,30 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
|
||||||
return HttpHelper.send(httpClient, request, makeEH(clazz));
|
return HttpHelper.send(httpClient, request, makeEH(clazz));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Type type) throws IOException {
|
||||||
|
return HttpHelper.send(httpClient, request, makeEH(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
|
||||||
|
private final Type type;
|
||||||
|
|
||||||
|
private SimpleErrorHandler(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
|
||||||
|
if (statusCode < 200 || statusCode >= 300) {
|
||||||
|
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
|
||||||
|
}
|
||||||
|
if (type == Void.class) {
|
||||||
|
return new HttpHelper.HttpOptional<>(null, null, statusCode);
|
||||||
|
}
|
||||||
|
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class SimpleError {
|
public static class SimpleError {
|
||||||
public String error;
|
public String error;
|
||||||
public int code;
|
public int code;
|
||||||
|
|
|
@ -2,43 +2,43 @@
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.events.RequestEvent;
|
||||||
import pro.gravit.launcher.managers.ConfigManager;
|
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
|
||||||
import pro.gravit.launcher.modules.events.ClosePhase;
|
import pro.gravit.launcher.base.modules.events.ClosePhase;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
|
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
|
||||||
import pro.gravit.launchserver.binary.EXEL4JLauncherBinary;
|
|
||||||
import pro.gravit.launchserver.binary.EXELauncherBinary;
|
import pro.gravit.launchserver.binary.EXELauncherBinary;
|
||||||
import pro.gravit.launchserver.binary.JARLauncherBinary;
|
import pro.gravit.launchserver.binary.JARLauncherBinary;
|
||||||
import pro.gravit.launchserver.binary.LauncherBinary;
|
import pro.gravit.launchserver.binary.LauncherBinary;
|
||||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||||
|
import pro.gravit.launchserver.helper.SignHelper;
|
||||||
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
|
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
|
||||||
import pro.gravit.launchserver.manangers.*;
|
import pro.gravit.launchserver.manangers.*;
|
||||||
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
||||||
import pro.gravit.launchserver.modules.events.*;
|
import pro.gravit.launchserver.modules.events.*;
|
||||||
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.launchserver.socket.SocketCommandServer;
|
||||||
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
|
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
|
||||||
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
||||||
import pro.gravit.utils.command.Command;
|
import pro.gravit.utils.command.Command;
|
||||||
import pro.gravit.utils.command.CommandHandler;
|
import pro.gravit.utils.command.CommandHandler;
|
||||||
import pro.gravit.utils.command.SubCommand;
|
import pro.gravit.utils.command.SubCommand;
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
import pro.gravit.utils.helper.JVMHelper;
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ProcessBuilder.Redirect;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.security.KeyStore;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +46,6 @@
|
||||||
* Not a singletron
|
* Not a singletron
|
||||||
*/
|
*/
|
||||||
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
|
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
|
||||||
public static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
|
|
||||||
/**
|
/**
|
||||||
* Working folder path
|
* Working folder path
|
||||||
*/
|
*/
|
||||||
|
@ -63,9 +62,11 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
||||||
* The path to the folder with compile-only libraries for the launcher
|
* The path to the folder with compile-only libraries for the launcher
|
||||||
*/
|
*/
|
||||||
public final Path launcherLibrariesCompile;
|
public final Path launcherLibrariesCompile;
|
||||||
|
public final Path launcherPack;
|
||||||
/**
|
/**
|
||||||
* The path to the folder with updates/webroot
|
* The path to the folder with updates/webroot
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final Path updatesDir;
|
public final Path updatesDir;
|
||||||
|
|
||||||
// Constant paths
|
// Constant paths
|
||||||
|
@ -76,8 +77,12 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
||||||
/**
|
/**
|
||||||
* The path to the folder with profiles
|
* The path to the folder with profiles
|
||||||
*/
|
*/
|
||||||
public final Path profilesDir;
|
|
||||||
public final Path tmpDir;
|
public final Path tmpDir;
|
||||||
|
public final Path modulesDir;
|
||||||
|
public final Path launcherModulesDir;
|
||||||
|
public final Path librariesDir;
|
||||||
|
public final Path controlFile;
|
||||||
|
public final Path proguardDir;
|
||||||
/**
|
/**
|
||||||
* This object contains runtime configuration
|
* This object contains runtime configuration
|
||||||
*/
|
*/
|
||||||
|
@ -90,8 +95,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
||||||
* Pipeline for building EXE
|
* Pipeline for building EXE
|
||||||
*/
|
*/
|
||||||
public final LauncherBinary launcherEXEBinary;
|
public final LauncherBinary launcherEXEBinary;
|
||||||
//public static LaunchServer server = null;
|
|
||||||
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
|
|
||||||
// Server config
|
// Server config
|
||||||
public final AuthHookManager authHookManager;
|
public final AuthHookManager authHookManager;
|
||||||
public final LaunchServerModulesManager modulesManager;
|
public final LaunchServerModulesManager modulesManager;
|
||||||
|
@ -108,23 +111,21 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
||||||
// Server
|
// Server
|
||||||
public final CommandHandler commandHandler;
|
public final CommandHandler commandHandler;
|
||||||
public final NettyServerSocketHandler nettyServerSocketHandler;
|
public final NettyServerSocketHandler nettyServerSocketHandler;
|
||||||
|
public final SocketCommandServer socketCommandServer;
|
||||||
public final ScheduledExecutorService service;
|
public final ScheduledExecutorService service;
|
||||||
public final AtomicBoolean started = new AtomicBoolean(false);
|
public final AtomicBoolean started = new AtomicBoolean(false);
|
||||||
public final LauncherModuleLoader launcherModuleLoader;
|
public final LauncherModuleLoader launcherModuleLoader;
|
||||||
private final Logger logger = LogManager.getLogger();
|
private final Logger logger = LogManager.getLogger();
|
||||||
|
public final int shardId;
|
||||||
public LaunchServerConfig config;
|
public LaunchServerConfig config;
|
||||||
// Updates and profiles
|
|
||||||
private volatile Set<ClientProfile> profilesList;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
|
||||||
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
|
|
||||||
this.dir = directories.dir;
|
this.dir = directories.dir;
|
||||||
this.tmpDir = directories.tmpDir;
|
this.tmpDir = directories.tmpDir;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.launchServerConfigManager = launchServerConfigManager;
|
this.launchServerConfigManager = launchServerConfigManager;
|
||||||
this.modulesManager = modulesManager;
|
this.modulesManager = modulesManager;
|
||||||
this.profilesDir = directories.profilesDir;
|
|
||||||
this.updatesDir = directories.updatesDir;
|
this.updatesDir = directories.updatesDir;
|
||||||
this.keyAgreementManager = keyAgreementManager;
|
this.keyAgreementManager = keyAgreementManager;
|
||||||
this.commandHandler = commandHandler;
|
this.commandHandler = commandHandler;
|
||||||
|
@ -133,6 +134,16 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
|
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
|
||||||
launcherLibraries = directories.launcherLibrariesDir;
|
launcherLibraries = directories.launcherLibrariesDir;
|
||||||
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
|
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
|
||||||
|
launcherPack = directories.launcherPackDir;
|
||||||
|
modulesDir = directories.modules;
|
||||||
|
launcherModulesDir = directories.launcherModules;
|
||||||
|
librariesDir = directories.librariesDir;
|
||||||
|
controlFile = directories.controlFile;
|
||||||
|
proguardDir = directories.proguardDir;
|
||||||
|
this.shardId = shardId;
|
||||||
|
if(!Files.isDirectory(launcherPack)) {
|
||||||
|
Files.createDirectories(launcherPack);
|
||||||
|
}
|
||||||
|
|
||||||
config.setLaunchServer(this);
|
config.setLaunchServer(this);
|
||||||
|
|
||||||
|
@ -140,9 +151,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
|
|
||||||
// Print keypair fingerprints
|
// Print keypair fingerprints
|
||||||
|
|
||||||
// Load class bindings.
|
|
||||||
launcherEXEBinaryClass = defaultLauncherEXEBinaryClass;
|
|
||||||
|
|
||||||
runtime.verify();
|
runtime.verify();
|
||||||
config.verify();
|
config.verify();
|
||||||
|
|
||||||
|
@ -183,6 +191,11 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
||||||
}
|
}
|
||||||
launcherModuleLoader.init();
|
launcherModuleLoader.init();
|
||||||
nettyServerSocketHandler = new NettyServerSocketHandler(this);
|
nettyServerSocketHandler = new NettyServerSocketHandler(this);
|
||||||
|
socketCommandServer = new SocketCommandServer(commandHandler, controlFile);
|
||||||
|
if(config.sign.checkCertificateExpired) {
|
||||||
|
checkCertificateExpired();
|
||||||
|
service.scheduleAtFixedRate(this::checkCertificateExpired, 24, 24, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
// post init modules
|
// post init modules
|
||||||
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
|
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
|
||||||
}
|
}
|
||||||
|
@ -210,7 +223,14 @@ public void reload(ReloadType type) throws Exception {
|
||||||
});
|
});
|
||||||
logger.debug("Init components successful");
|
logger.debug("Init components successful");
|
||||||
}
|
}
|
||||||
|
if(!type.equals(ReloadType.NO_AUTH)) {
|
||||||
|
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
|
||||||
|
Client client = wsHandler.getClient();
|
||||||
|
if(client.auth != null) {
|
||||||
|
client.auth = config.getAuthProviderPair(client.auth_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -225,9 +245,8 @@ public void invoke(String... args) throws Exception {
|
||||||
}
|
}
|
||||||
switch (args[0]) {
|
switch (args[0]) {
|
||||||
case "full" -> reload(ReloadType.FULL);
|
case "full" -> reload(ReloadType.FULL);
|
||||||
case "no_auth" -> reload(ReloadType.NO_AUTH);
|
|
||||||
case "no_components" -> reload(ReloadType.NO_COMPONENTS);
|
case "no_components" -> reload(ReloadType.NO_COMPONENTS);
|
||||||
default -> reload(ReloadType.FULL);
|
default -> reload(ReloadType.NO_AUTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -253,26 +272,37 @@ public void invoke(String... args) throws Exception {
|
||||||
}
|
}
|
||||||
pair.core.close();
|
pair.core.close();
|
||||||
pair.core = new RejectAuthCoreProvider();
|
pair.core = new RejectAuthCoreProvider();
|
||||||
pair.core.init(instance);
|
pair.core.init(instance, pair);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
commands.put("resetauth", resetauth);
|
commands.put("resetauth", resetauth);
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LauncherBinary binary() {
|
public void checkCertificateExpired() {
|
||||||
if (launcherEXEBinaryClass != null) {
|
if(!config.sign.enabled) {
|
||||||
try {
|
return;
|
||||||
return (LauncherBinary) MethodHandles.publicLookup().findConstructor(launcherEXEBinaryClass, MethodType.methodType(void.class, LaunchServer.class)).invoke(this);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Class.forName("net.sf.launch4j.Builder");
|
KeyStore keyStore = SignHelper.getStore(Paths.get(config.sign.keyStore), config.sign.keyStorePass, config.sign.keyStoreType);
|
||||||
if (config.launch4j.enabled) return new EXEL4JLauncherBinary(this);
|
Instant date = SignHelper.getCertificateExpired(keyStore, config.sign.keyAlias);
|
||||||
} catch (ClassNotFoundException ignored) {
|
if(date == null) {
|
||||||
logger.warn("Launch4J isn't in classpath.");
|
logger.debug("The certificate will expire at unlimited");
|
||||||
|
} else if(date.minus(Duration.ofDays(30)).isBefore(Instant.now())) {
|
||||||
|
logger.warn("The certificate will expire at {}", date.toString());
|
||||||
|
} else {
|
||||||
|
logger.debug("The certificate will expire at {}", date.toString());
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("Can't get certificate expire date", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LauncherBinary binary() {
|
||||||
|
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
|
||||||
|
modulesManager.invokeEvent(event);
|
||||||
|
if(event.binary != null) {
|
||||||
|
return event.binary;
|
||||||
}
|
}
|
||||||
return new EXELauncherBinary(this);
|
return new EXELauncherBinary(this);
|
||||||
}
|
}
|
||||||
|
@ -295,12 +325,14 @@ public void close() throws Exception {
|
||||||
logger.info("LaunchServer stopped");
|
logger.info("LaunchServer stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Set<ClientProfile> getProfiles() {
|
public Set<ClientProfile> getProfiles() {
|
||||||
return profilesList;
|
return config.profileProvider.getProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setProfiles(Set<ClientProfile> profilesList) {
|
public void setProfiles(Set<ClientProfile> profilesList) {
|
||||||
this.profilesList = Collections.unmodifiableSet(profilesList);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rebindNettyServerSocket() {
|
public void rebindNettyServerSocket() {
|
||||||
|
@ -323,17 +355,17 @@ public void run() {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
CommonHelper.newThread("Command Thread", true, commandHandler).start();
|
CommonHelper.newThread("Command Thread", true, commandHandler).start();
|
||||||
|
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
|
||||||
// Sync updates dir
|
// Sync updates dir
|
||||||
CommonHelper.newThread("Profiles and updates sync", true, () -> {
|
CommonHelper.newThread("Profiles and updates sync", true, () -> {
|
||||||
try {
|
try {
|
||||||
if (!IOHelper.isDir(updatesDir))
|
|
||||||
Files.createDirectory(updatesDir);
|
|
||||||
updatesManager.readUpdatesDir();
|
|
||||||
|
|
||||||
// Sync profiles dir
|
// Sync profiles dir
|
||||||
if (!IOHelper.isDir(profilesDir))
|
|
||||||
Files.createDirectory(profilesDir);
|
|
||||||
syncProfilesDir();
|
syncProfilesDir();
|
||||||
|
|
||||||
|
// Sync updates dir
|
||||||
|
config.updatesProvider.syncInitially();
|
||||||
|
|
||||||
|
|
||||||
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
|
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Updates/Profiles not synced", e);
|
logger.error("Updates/Profiles not synced", e);
|
||||||
|
@ -361,40 +393,38 @@ public void syncLauncherBinaries() throws IOException {
|
||||||
|
|
||||||
// Syncing launcher EXE binary
|
// Syncing launcher EXE binary
|
||||||
logger.info("Syncing launcher EXE binary file");
|
logger.info("Syncing launcher EXE binary file");
|
||||||
if (!launcherEXEBinary.sync() && config.launch4j.enabled)
|
if (!launcherEXEBinary.sync())
|
||||||
logger.warn("Missing launcher EXE binary file");
|
logger.warn("Missing launcher EXE binary file");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void syncProfilesDir() throws IOException {
|
public void syncProfilesDir() throws IOException {
|
||||||
logger.info("Syncing profiles dir");
|
logger.info("Syncing profiles dir");
|
||||||
List<ClientProfile> newProfies = new LinkedList<>();
|
config.profileProvider.sync();
|
||||||
IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false);
|
if (config.netty.sendProfileUpdatesEvent) {
|
||||||
|
sendUpdateProfilesEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sort and set new profiles
|
private void sendUpdateProfilesEvent() {
|
||||||
newProfies.sort(Comparator.comparing(a -> a));
|
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
|
||||||
profilesList = Set.copyOf(newProfies);
|
return;
|
||||||
|
}
|
||||||
|
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
|
||||||
|
Client client = handler.getClient();
|
||||||
|
if (client == null || !client.isAuth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
|
||||||
|
event.requestUUID = RequestEvent.eventUUID;
|
||||||
|
handler.service.sendObject(ch, event);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
||||||
updatesManager.syncUpdatesDir(dirs);
|
updatesManager.syncUpdatesDir(dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void restart() {
|
|
||||||
ProcessBuilder builder = new ProcessBuilder();
|
|
||||||
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));
|
|
||||||
else throw new IllegalArgumentException("Please create start script and link it as startScript in config.");
|
|
||||||
builder.directory(this.dir.toFile());
|
|
||||||
builder.inheritIO();
|
|
||||||
builder.redirectErrorStream(true);
|
|
||||||
builder.redirectOutput(Redirect.PIPE);
|
|
||||||
try {
|
|
||||||
builder.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Restart failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerObject(String name, Object object) {
|
public void registerObject(String name, Object object) {
|
||||||
if (object instanceof Reconfigurable) {
|
if (object instanceof Reconfigurable) {
|
||||||
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
|
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
|
||||||
|
@ -407,11 +437,6 @@ public void unregisterObject(String name, Object object) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fullyRestart() {
|
|
||||||
restart();
|
|
||||||
JVMHelper.RUNTIME.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public enum ReloadType {
|
public enum ReloadType {
|
||||||
NO_AUTH,
|
NO_AUTH,
|
||||||
|
@ -436,54 +461,42 @@ public interface LaunchServerConfigManager {
|
||||||
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
|
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
private final Collection<ClientProfile> result;
|
|
||||||
private final Logger logger = LogManager.getLogger();
|
|
||||||
|
|
||||||
private ProfilesFileVisitor(Collection<ClientProfile> result) {
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
|
|
||||||
|
|
||||||
// Read profile
|
|
||||||
ClientProfile profile;
|
|
||||||
try (BufferedReader reader = IOHelper.newReader(file)) {
|
|
||||||
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
|
|
||||||
}
|
|
||||||
profile.verify();
|
|
||||||
|
|
||||||
// Add SIGNED profile to result list
|
|
||||||
result.add(profile);
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LaunchServerDirectories {
|
public static class LaunchServerDirectories {
|
||||||
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
|
public static final String UPDATES_NAME = "updates",
|
||||||
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
|
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
|
||||||
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", KEY_NAME = ".keys";
|
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack",
|
||||||
|
KEY_NAME = ".keys", MODULES = "modules", LAUNCHER_MODULES = "launcher-modules",
|
||||||
|
LIBRARIES = "libraries", CONTROL_FILE = "control-file", PROGUARD_DIR = "proguard-libraries";
|
||||||
public Path updatesDir;
|
public Path updatesDir;
|
||||||
public Path profilesDir;
|
public Path librariesDir;
|
||||||
public Path launcherLibrariesDir;
|
public Path launcherLibrariesDir;
|
||||||
public Path launcherLibrariesCompileDir;
|
public Path launcherLibrariesCompileDir;
|
||||||
|
public Path launcherPackDir;
|
||||||
public Path keyDirectory;
|
public Path keyDirectory;
|
||||||
|
public Path proguardDir;
|
||||||
public Path dir;
|
public Path dir;
|
||||||
public Path trustStore;
|
public Path trustStore;
|
||||||
public Path tmpDir;
|
public Path tmpDir;
|
||||||
|
public Path modules;
|
||||||
|
public Path launcherModules;
|
||||||
|
public Path controlFile;
|
||||||
|
|
||||||
public void collect() {
|
public void collect() {
|
||||||
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
|
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
|
||||||
if (profilesDir == null) profilesDir = getPath(PROFILES_NAME);
|
|
||||||
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
|
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
|
||||||
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
|
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
|
||||||
if (launcherLibrariesCompileDir == null)
|
if (launcherLibrariesCompileDir == null)
|
||||||
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
|
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
|
||||||
|
if (launcherPackDir == null)
|
||||||
|
launcherPackDir = getPath(LAUNCHERPACK_NAME);
|
||||||
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
|
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
|
||||||
|
if (modules == null) modules = getPath(MODULES);
|
||||||
|
if (launcherModules == null) launcherModules = getPath(LAUNCHER_MODULES);
|
||||||
|
if (librariesDir == null) librariesDir = getPath(LIBRARIES);
|
||||||
|
if (controlFile == null) controlFile = getPath(CONTROL_FILE);
|
||||||
|
if (proguardDir == null) proguardDir = getPath(PROGUARD_DIR);
|
||||||
if (tmpDir == null)
|
if (tmpDir == null)
|
||||||
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));
|
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("launchserver-%s".formatted(SecurityHelper.randomStringToken()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path getPath(String dirName) {
|
private Path getPath(String dirName) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class LaunchServerBuilder {
|
||||||
private KeyAgreementManager keyAgreementManager;
|
private KeyAgreementManager keyAgreementManager;
|
||||||
private CertificateManager certificateManager;
|
private CertificateManager certificateManager;
|
||||||
private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
|
private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
|
||||||
|
private Integer shardId;
|
||||||
|
|
||||||
public LaunchServerBuilder setConfig(LaunchServerConfig config) {
|
public LaunchServerBuilder setConfig(LaunchServerConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
@ -55,6 +56,11 @@ public LaunchServerBuilder setDir(Path dir) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LaunchServerBuilder setShardId(Integer shardId) {
|
||||||
|
this.shardId = shardId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) {
|
public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) {
|
||||||
this.launchServerConfigManager = launchServerConfigManager;
|
this.launchServerConfigManager = launchServerConfigManager;
|
||||||
return this;
|
return this;
|
||||||
|
@ -63,32 +69,15 @@ public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServe
|
||||||
public LaunchServer build() throws Exception {
|
public LaunchServer build() throws Exception {
|
||||||
directories.collect();
|
directories.collect();
|
||||||
if (launchServerConfigManager == null) {
|
if (launchServerConfigManager == null) {
|
||||||
launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
|
launchServerConfigManager = new NullLaunchServerConfigManager();
|
||||||
@Override
|
|
||||||
public LaunchServerConfig readConfig() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeConfig(LaunchServerConfig config) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (keyAgreementManager == null) {
|
if (keyAgreementManager == null) {
|
||||||
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
|
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
|
||||||
}
|
}
|
||||||
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager);
|
if(shardId == null) {
|
||||||
|
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0"));
|
||||||
|
}
|
||||||
|
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
|
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
|
||||||
|
@ -99,4 +88,26 @@ public LaunchServerBuilder setCertificateManager(CertificateManager certificateM
|
||||||
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
|
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
|
||||||
this.keyAgreementManager = keyAgreementManager;
|
this.keyAgreementManager = keyAgreementManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NullLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
||||||
|
@Override
|
||||||
|
public LaunchServerConfig readConfig() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeConfig(LaunchServerConfig config) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,20 @@
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.LauncherTrustManager;
|
import pro.gravit.launcher.core.LauncherTrustManager;
|
||||||
import pro.gravit.launcher.modules.events.PreConfigPhase;
|
import pro.gravit.launcher.base.modules.events.PreConfigPhase;
|
||||||
import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
|
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
|
||||||
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
|
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
|
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
|
||||||
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
|
import pro.gravit.launchserver.auth.mix.MixProvider;
|
||||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||||
|
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
|
||||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
||||||
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
||||||
|
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
|
||||||
import pro.gravit.launchserver.components.Component;
|
import pro.gravit.launchserver.components.Component;
|
||||||
import pro.gravit.launchserver.config.LaunchServerConfig;
|
import pro.gravit.launchserver.config.LaunchServerConfig;
|
||||||
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
|
||||||
|
@ -28,14 +31,12 @@
|
||||||
import pro.gravit.utils.helper.JVMHelper;
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class LaunchServerStarter {
|
public class LaunchServerStarter {
|
||||||
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
|
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
|
||||||
|
@ -43,27 +44,25 @@ public class LaunchServerStarter {
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
JVMHelper.checkStackTrace(LaunchServerStarter.class);
|
JVMHelper.verifySystemProperties(LaunchServer.class, false);
|
||||||
JVMHelper.verifySystemProperties(LaunchServer.class, true);
|
|
||||||
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
|
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
|
||||||
LogHelper.printVersion("LaunchServer");
|
LogHelper.printVersion("LaunchServer");
|
||||||
LogHelper.printLicense("LaunchServer");
|
LogHelper.printLicense("LaunchServer");
|
||||||
if (!StarterAgent.isAgentStarted()) {
|
|
||||||
LogHelper.error("StarterAgent is not started!");
|
|
||||||
LogHelper.error("You should add to JVM options this option: `-javaagent:LaunchServer.jar`");
|
|
||||||
}
|
|
||||||
Path dir = IOHelper.WORKING_DIR;
|
Path dir = IOHelper.WORKING_DIR;
|
||||||
Path configFile, runtimeConfigFile;
|
Path configFile, runtimeConfigFile;
|
||||||
try {
|
try {
|
||||||
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
} catch (ClassNotFoundException ex) {
|
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
|
||||||
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
|
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
|
||||||
|
directories.dir = dir;
|
||||||
|
directories.collect();
|
||||||
CertificateManager certificateManager = new CertificateManager();
|
CertificateManager certificateManager = new CertificateManager();
|
||||||
try {
|
try {
|
||||||
certificateManager.readTrustStore(dir.resolve("truststore"));
|
certificateManager.readTrustStore(directories.trustStore);
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
@ -84,11 +83,12 @@ public static void main(String[] args) throws Exception {
|
||||||
LaunchServerRuntimeConfig runtimeConfig;
|
LaunchServerRuntimeConfig runtimeConfig;
|
||||||
LaunchServerConfig config;
|
LaunchServerConfig config;
|
||||||
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
|
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
|
||||||
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
|
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
|
||||||
modulesManager.autoload();
|
modulesManager.autoload();
|
||||||
modulesManager.initModules(null);
|
modulesManager.initModules(null);
|
||||||
registerAll();
|
registerAll();
|
||||||
initGson(modulesManager);
|
initGson(modulesManager);
|
||||||
|
printExperimentalBranch();
|
||||||
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
|
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
|
||||||
configFile = dir.resolve("LaunchServer.conf");
|
configFile = dir.resolve("LaunchServer.conf");
|
||||||
} else {
|
} else {
|
||||||
|
@ -127,49 +127,7 @@ public static void main(String[] args) throws Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
|
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
|
||||||
@Override
|
|
||||||
public LaunchServerConfig readConfig() throws IOException {
|
|
||||||
LaunchServerConfig config1;
|
|
||||||
try (BufferedReader reader = IOHelper.newReader(configFile)) {
|
|
||||||
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
|
|
||||||
}
|
|
||||||
return config1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
|
||||||
LaunchServerRuntimeConfig config1;
|
|
||||||
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
|
||||||
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
|
||||||
}
|
|
||||||
return config1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeConfig(LaunchServerConfig config) throws IOException {
|
|
||||||
try (Writer writer = IOHelper.newWriter(configFile)) {
|
|
||||||
if (Launcher.gsonManager.configGson != null) {
|
|
||||||
Launcher.gsonManager.configGson.toJson(config, writer);
|
|
||||||
} else {
|
|
||||||
logger.error("Error writing LaunchServer runtime config file. Gson is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
|
|
||||||
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
|
|
||||||
if (Launcher.gsonManager.configGson != null) {
|
|
||||||
Launcher.gsonManager.configGson.toJson(config, writer);
|
|
||||||
} else {
|
|
||||||
logger.error("Error writing LaunchServer runtime config file. Gson is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
|
|
||||||
directories.dir = dir;
|
|
||||||
LaunchServer server = new LaunchServerBuilder()
|
LaunchServer server = new LaunchServerBuilder()
|
||||||
.setDirectories(directories)
|
.setDirectories(directories)
|
||||||
.setEnv(env)
|
.setEnv(env)
|
||||||
|
@ -180,7 +138,24 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOExcept
|
||||||
.setLaunchServerConfigManager(launchServerConfigManager)
|
.setLaunchServerConfigManager(launchServerConfigManager)
|
||||||
.setCertificateManager(certificateManager)
|
.setCertificateManager(certificateManager)
|
||||||
.build();
|
.build();
|
||||||
if (!prepareMode) {
|
List<String> allArgs = List.of(args);
|
||||||
|
boolean isPrepareMode = prepareMode || allArgs.contains("--prepare");
|
||||||
|
boolean isRunCommand = false;
|
||||||
|
String runCommand = null;
|
||||||
|
for(var e : allArgs) {
|
||||||
|
if(e.equals("--run")) {
|
||||||
|
isRunCommand = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(isRunCommand) {
|
||||||
|
runCommand = e;
|
||||||
|
isRunCommand = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(runCommand != null) {
|
||||||
|
localCommandHandler.eval(runCommand, false);
|
||||||
|
}
|
||||||
|
if (!isPrepareMode) {
|
||||||
server.run();
|
server.run();
|
||||||
} else {
|
} else {
|
||||||
server.close();
|
server.close();
|
||||||
|
@ -192,7 +167,6 @@ public static void initGson(LaunchServerModulesManager modulesManager) {
|
||||||
Launcher.gsonManager.initGson();
|
Launcher.gsonManager.initGson();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static void registerAll() {
|
public static void registerAll() {
|
||||||
AuthCoreProvider.registerProviders();
|
AuthCoreProvider.registerProviders();
|
||||||
PasswordVerifier.registerProviders();
|
PasswordVerifier.registerProviders();
|
||||||
|
@ -204,6 +178,29 @@ public static void registerAll() {
|
||||||
GetAvailabilityAuthRequest.registerProviders();
|
GetAvailabilityAuthRequest.registerProviders();
|
||||||
OptionalAction.registerProviders();
|
OptionalAction.registerProviders();
|
||||||
OptionalTrigger.registerProviders();
|
OptionalTrigger.registerProviders();
|
||||||
|
MixProvider.registerProviders();
|
||||||
|
ProfileProvider.registerProviders();
|
||||||
|
UpdatesProvider.registerProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printExperimentalBranch() {
|
||||||
|
try(Reader reader = IOHelper.newReader(IOHelper.getResourceURL("experimental-build.json"))) {
|
||||||
|
ExperimentalBuild info = Launcher.gsonManager.configGson.fromJson(reader, ExperimentalBuild.class);
|
||||||
|
if(info.features == null || info.features.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.warn("This is experimental build. Please do not use this in production");
|
||||||
|
logger.warn("Experimental features: [{}]", String.join(",", info.features));
|
||||||
|
for(var e : info.info) {
|
||||||
|
logger.warn(e);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Build information not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record ExperimentalBuild(List<String> features, List<String> info) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
|
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
|
||||||
|
@ -226,7 +223,7 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
||||||
address = System.getProperty("launchserver.address", null);
|
address = System.getProperty("launchserver.address", null);
|
||||||
}
|
}
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
System.out.println("LaunchServer address(default: localhost): ");
|
System.out.println("External launchServer address:port (default: localhost:9274): ");
|
||||||
address = commandHandler.readLine();
|
address = commandHandler.readLine();
|
||||||
}
|
}
|
||||||
String projectName = System.getenv("PROJECTNAME");
|
String projectName = System.getenv("PROJECTNAME");
|
||||||
|
@ -240,18 +237,29 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
||||||
newConfig.setProjectName(projectName);
|
newConfig.setProjectName(projectName);
|
||||||
}
|
}
|
||||||
if (address == null || address.isEmpty()) {
|
if (address == null || address.isEmpty()) {
|
||||||
logger.error("Address null. Using localhost");
|
logger.error("Address null. Using localhost:9274");
|
||||||
address = "localhost";
|
address = "localhost:9274";
|
||||||
}
|
}
|
||||||
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
|
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
|
||||||
logger.error("ProjectName null. Using MineCraft");
|
logger.error("ProjectName null. Using MineCraft");
|
||||||
newConfig.projectName = "MineCraft";
|
newConfig.projectName = "MineCraft";
|
||||||
}
|
}
|
||||||
|
int port = 9274;
|
||||||
newConfig.netty.address = "ws://" + address + ":9274/api";
|
if(address.contains(":")) {
|
||||||
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
|
String portString = address.substring(address.indexOf(':')+1);
|
||||||
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
|
try {
|
||||||
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
|
port = Integer.parseInt(portString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.warn("Unknown port {}, using 9274", portString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("Address {} doesn't contains port (you want to use nginx?)", address);
|
||||||
|
}
|
||||||
|
newConfig.netty.address = "ws://" + address + "/api";
|
||||||
|
newConfig.netty.downloadURL = "http://" + address + "/%dirname%/";
|
||||||
|
newConfig.netty.launcherURL = "http://" + address + "/Launcher.jar";
|
||||||
|
newConfig.netty.launcherEXEURL = "http://" + address + "/Launcher.exe";
|
||||||
|
newConfig.netty.binds[0].port = port;
|
||||||
|
|
||||||
// Write LaunchServer config
|
// Write LaunchServer config
|
||||||
logger.info("Writing LaunchServer config file");
|
logger.info("Writing LaunchServer config file");
|
||||||
|
@ -259,4 +267,64 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
||||||
Launcher.gsonManager.configGson.toJson(newConfig, writer);
|
Launcher.gsonManager.configGson.toJson(newConfig, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
||||||
|
private final Path configFile;
|
||||||
|
private final Path runtimeConfigFile;
|
||||||
|
|
||||||
|
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
|
||||||
|
this.configFile = configFile;
|
||||||
|
this.runtimeConfigFile = runtimeConfigFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LaunchServerConfig readConfig() throws IOException {
|
||||||
|
LaunchServerConfig config1;
|
||||||
|
try (BufferedReader reader = IOHelper.newReader(configFile)) {
|
||||||
|
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
|
||||||
|
}
|
||||||
|
return config1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
||||||
|
LaunchServerRuntimeConfig config1;
|
||||||
|
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
||||||
|
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
||||||
|
}
|
||||||
|
return config1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeConfig(LaunchServerConfig config) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
try (Writer writer = IOHelper.newWriter(output)) {
|
||||||
|
if (Launcher.gsonManager.configGson != null) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(config, writer);
|
||||||
|
} else {
|
||||||
|
logger.error("Error writing LaunchServer config file. Gson is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] bytes = output.toByteArray();
|
||||||
|
if(bytes.length > 0) {
|
||||||
|
IOHelper.write(configFile, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
try (Writer writer = IOHelper.newWriter(output)) {
|
||||||
|
if (Launcher.gsonManager.configGson != null) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(config, writer);
|
||||||
|
} else {
|
||||||
|
logger.error("Error writing LaunchServer runtime config file. Gson is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] bytes = output.toByteArray();
|
||||||
|
if(bytes.length > 0) {
|
||||||
|
IOHelper.write(runtimeConfigFile, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
96
LaunchServer/src/main/java/pro/gravit/launchserver/Main.java
Normal file
96
LaunchServer/src/main/java/pro/gravit/launchserver/Main.java
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package pro.gravit.launchserver;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.holder.LaunchServerControlHolder;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
import pro.gravit.utils.launch.ClassLoaderControl;
|
||||||
|
import pro.gravit.utils.launch.LaunchOptions;
|
||||||
|
import pro.gravit.utils.launch.ModuleLaunch;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.FileVisitOption;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
private static final List<String> classpathOnly = List.of("proguard", "progressbar", "kotlin");
|
||||||
|
private static final String LOG4J_PROPERTY = "log4j2.configurationFile";
|
||||||
|
private static final String DEBUG_PROPERTY = "launchserver.main.debug";
|
||||||
|
private static final String LIBRARIES_PROPERTY = "launchserver.dir.libraries";
|
||||||
|
private static boolean isClasspathOnly(Path path) {
|
||||||
|
var fileName = path.getFileName().toString();
|
||||||
|
for(var e : classpathOnly) {
|
||||||
|
if(fileName.contains(e)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void unpackLog4j() {
|
||||||
|
String log4jConfigurationFile = System.getProperty(LOG4J_PROPERTY);
|
||||||
|
if(log4jConfigurationFile == null) {
|
||||||
|
Path log4jConfigPath = Path.of("log4j2.xml");
|
||||||
|
if(!Files.exists(log4jConfigPath)) {
|
||||||
|
try(FileOutputStream output = new FileOutputStream(log4jConfigPath.toFile())) {
|
||||||
|
try(InputStream input = Main.class.getResourceAsStream("/log4j2.xml")) {
|
||||||
|
if(input == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input.transferTo(output);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.setProperty(LOG4J_PROPERTY, log4jConfigPath.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
unpackLog4j();
|
||||||
|
ModuleLaunch launch = new ModuleLaunch();
|
||||||
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
options.moduleConf = new LaunchOptions.ModuleConf();
|
||||||
|
Path librariesPath = Path.of(System.getProperty(LIBRARIES_PROPERTY, "libraries"));
|
||||||
|
List<Path> libraries;
|
||||||
|
try(Stream<Path> files = Files.walk(librariesPath, FileVisitOption.FOLLOW_LINKS)) {
|
||||||
|
libraries = new ArrayList<>(files.filter(e -> e.getFileName().toString().endsWith(".jar")).toList());
|
||||||
|
}
|
||||||
|
List<Path> classpath = new ArrayList<>();
|
||||||
|
List<String> modulepath = new ArrayList<>();
|
||||||
|
for(var l : libraries) {
|
||||||
|
if(isClasspathOnly(l)) {
|
||||||
|
classpath.add(l);
|
||||||
|
} else {
|
||||||
|
modulepath.add(l.toAbsolutePath().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classpath.add(IOHelper.getCodeSource(LaunchServerStarter.class));
|
||||||
|
options.moduleConf.modulePath.addAll(modulepath);
|
||||||
|
options.moduleConf.modules.add("ALL-MODULE-PATH");
|
||||||
|
options.moduleConf.enableNativeAccess.add("org.fusesource.jansi");
|
||||||
|
options.moduleConf.enableNativeAccess.add("io.netty.common");
|
||||||
|
ClassLoaderControl control = launch.init(classpath, "natives", options);
|
||||||
|
control.clearLauncherPackages();
|
||||||
|
control.addLauncherPackage("pro.gravit.utils.launch");
|
||||||
|
control.addLauncherPackage("pro.gravit.launchserver.holder");
|
||||||
|
ModuleLayer.Controller controller = (ModuleLayer.Controller) control.getJava9ModuleController();
|
||||||
|
LaunchServerControlHolder.setControl(control);
|
||||||
|
LaunchServerControlHolder.setController(controller);
|
||||||
|
if(Boolean.getBoolean(DEBUG_PROPERTY)) {
|
||||||
|
for(var e : controller.layer().modules()) {
|
||||||
|
System.out.printf("Module %s\n", e.getName());
|
||||||
|
for(var p : e.getPackages()) {
|
||||||
|
System.out.printf("Package %s\n", p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch.launch("pro.gravit.launchserver.LaunchServerStarter", null, Arrays.asList(args));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
package pro.gravit.launchserver;
|
package pro.gravit.launchserver;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
public final class StarterAgent {
|
public final class StarterAgent {
|
||||||
|
|
||||||
|
@ -20,47 +14,6 @@ public static boolean isAgentStarted() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void premain(String agentArgument, Instrumentation inst) {
|
public static void premain(String agentArgument, Instrumentation inst) {
|
||||||
StarterAgent.inst = inst;
|
throw new UnsupportedOperationException("Please remove -javaagent option from start.sh");
|
||||||
libraries = Paths.get(Optional.ofNullable(agentArgument).map(String::trim).filter(e -> !e.isEmpty()).orElse("libraries"));
|
|
||||||
isStarted = true;
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(libraries, Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new StarterVisitor());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class StarterVisitor extends SimpleFileVisitor<Path> {
|
|
||||||
private static final Set<PosixFilePermission> DPERMS;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Set<PosixFilePermission> perms = new HashSet<>(Arrays.asList(PosixFilePermission.values()));
|
|
||||||
perms.remove(PosixFilePermission.OTHERS_WRITE);
|
|
||||||
perms.remove(PosixFilePermission.GROUP_WRITE);
|
|
||||||
DPERMS = Collections.unmodifiableSet(perms);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final boolean fixLib;
|
|
||||||
|
|
||||||
private StarterVisitor() {
|
|
||||||
Path filef = StarterAgent.libraries.resolve(".libraries_chmoded");
|
|
||||||
this.fixLib = !Files.exists(filef) && !Boolean.getBoolean("launcher.noLibrariesPosixPermsFix");
|
|
||||||
if (fixLib) {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(filef);
|
|
||||||
Files.createFile(filef);
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
if (fixLib && Files.getFileAttributeView(file, PosixFileAttributeView.class) != null)
|
|
||||||
Files.setPosixFilePermissions(file, DPERMS);
|
|
||||||
if (file.toFile().getName().endsWith(".jar"))
|
|
||||||
inst.appendToSystemClassLoaderSearch(new JarFile(file.toFile()));
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package pro.gravit.launchserver.asm;
|
package pro.gravit.launchserver.asm;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassVisitor;
|
import org.objectweb.asm.ClassVisitor;
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,14 +19,32 @@
|
||||||
* чего угодно. Работает через поиск class-файлов в classpath.
|
* чего угодно. Работает через поиск class-файлов в classpath.
|
||||||
*/
|
*/
|
||||||
public class ClassMetadataReader implements Closeable {
|
public class ClassMetadataReader implements Closeable {
|
||||||
|
private final Logger logger = LogManager.getLogger(ClassMetadataReader.class);
|
||||||
private final List<JarFile> cp;
|
private final List<JarFile> cp;
|
||||||
|
private final Map<String, Module> moduleClassFinder;
|
||||||
|
|
||||||
public ClassMetadataReader(List<JarFile> cp) {
|
public ClassMetadataReader(List<JarFile> cp) {
|
||||||
this.cp = cp;
|
this.cp = cp;
|
||||||
|
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
|
||||||
|
var moduleLayer = ModuleLayer.boot();
|
||||||
|
moduleClassFinder = collectModulePackages(moduleLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassMetadataReader() {
|
public ClassMetadataReader() {
|
||||||
this.cp = new ArrayList<>();
|
this.cp = new ArrayList<>();
|
||||||
|
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
|
||||||
|
var moduleLayer = ModuleLayer.boot();
|
||||||
|
moduleClassFinder = collectModulePackages(moduleLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Module> collectModulePackages(ModuleLayer layer) {
|
||||||
|
var map = new HashMap<String, Module>();
|
||||||
|
for(var m : layer.modules()) {
|
||||||
|
for(var p : m.getPackages()) {
|
||||||
|
map.put(p, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JarFile> getCp() {
|
public List<JarFile> getCp() {
|
||||||
|
@ -58,7 +77,42 @@ public byte[] getClassData(String className) throws IOException {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return IOHelper.read(IOHelper.getResourceURL(className + ".class"));
|
if(ClassMetadataReader.class.getModule().isNamed()) {
|
||||||
|
String pkg = getClassPackage(className).replace('/', '.');
|
||||||
|
var module = moduleClassFinder.get(pkg);
|
||||||
|
if(module != null) {
|
||||||
|
var cl = module.getClassLoader();
|
||||||
|
if(cl == null) {
|
||||||
|
cl = ClassLoader.getPlatformClassLoader();
|
||||||
|
}
|
||||||
|
var stream = cl.getResourceAsStream(className+".class");
|
||||||
|
if(stream != null) {
|
||||||
|
try(stream) {
|
||||||
|
return IOHelper.read(stream);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new FileNotFoundException("Class "+className + ".class");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new FileNotFoundException("Package "+pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stream = ClassLoader.getSystemClassLoader().getResourceAsStream(className+".class");
|
||||||
|
if(stream != null) {
|
||||||
|
try(stream) {
|
||||||
|
return IOHelper.read(stream);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new FileNotFoundException(className + ".class");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassPackage(String type) {
|
||||||
|
int idx = type.lastIndexOf("/");
|
||||||
|
if(idx <= 0) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
return type.substring(0, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSuperClass(String type) {
|
public String getSuperClass(String type) {
|
||||||
|
@ -66,6 +120,7 @@ public String getSuperClass(String type) {
|
||||||
try {
|
try {
|
||||||
return getSuperClassASM(type);
|
return getSuperClassASM(type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.warn("getSuperClass: type {} not found ({}: {})", type, e.getClass().getName(), e.getMessage());
|
||||||
return "java/lang/Object";
|
return "java/lang/Object";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +155,7 @@ private static class CheckSuperClassVisitor extends ClassVisitor {
|
||||||
String superClassName;
|
String superClassName;
|
||||||
|
|
||||||
public CheckSuperClassVisitor() {
|
public CheckSuperClassVisitor() {
|
||||||
super(Opcodes.ASM7);
|
super(Opcodes.ASM9);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
import org.objectweb.asm.tree.*;
|
import org.objectweb.asm.tree.*;
|
||||||
import pro.gravit.launcher.LauncherInject;
|
import pro.gravit.launcher.core.LauncherInject;
|
||||||
import pro.gravit.launcher.LauncherInjectionConstructor;
|
import pro.gravit.launcher.core.LauncherInjectionConstructor;
|
||||||
import pro.gravit.launchserver.binary.BuildContext;
|
import pro.gravit.launchserver.binary.BuildContext;
|
||||||
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
|
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
|
||||||
|
@ -65,9 +64,9 @@ private static void visit(ClassNode classNode, Map<String, Object> values) {
|
||||||
return newClinitMethod;
|
return newClinitMethod;
|
||||||
});
|
});
|
||||||
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
|
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
|
||||||
.collect(Collectors.toList());
|
.toList();
|
||||||
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
|
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
|
||||||
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
|
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
|
||||||
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
|
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
|
||||||
classNode.fields.forEach(field -> {
|
classNode.fields.forEach(field -> {
|
||||||
// Notice that fields that will be used with this algo should not have default
|
// Notice that fields that will be used with this algo should not have default
|
||||||
|
@ -92,7 +91,7 @@ public void visit(final String name, final Object value) {
|
||||||
if ("value".equals(name)) {
|
if ("value".equals(name)) {
|
||||||
if (value.getClass() != String.class)
|
if (value.getClass() != String.class)
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
String.format("Invalid annotation with value class %s", field.getClass().getName()));
|
"Invalid annotation with value class %s".formatted(field.getClass().getName()));
|
||||||
valueName.set(value.toString());
|
valueName.set(value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,7 +111,7 @@ public void visit(final String name, final Object value) {
|
||||||
}
|
}
|
||||||
List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray())
|
List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray())
|
||||||
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
|
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
|
||||||
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList();
|
||||||
InsnList setter = serializeValue(value);
|
InsnList setter = serializeValue(value);
|
||||||
if (putStaticNodes.isEmpty()) {
|
if (putStaticNodes.isEmpty()) {
|
||||||
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
|
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
|
||||||
|
@ -126,11 +125,11 @@ public void visit(final String name, final Object value) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (initMethod == null) {
|
if (initMethod == null) {
|
||||||
throw new IllegalArgumentException(String.format("Not found init in target: %s", classNode.name));
|
throw new IllegalArgumentException("Not found init in target: %s".formatted(classNode.name));
|
||||||
}
|
}
|
||||||
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
|
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
|
||||||
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
|
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
|
||||||
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
|
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList();
|
||||||
InsnList setter = serializeValue(value);
|
InsnList setter = serializeValue(value);
|
||||||
if (putFieldNodes.isEmpty()) {
|
if (putFieldNodes.isEmpty()) {
|
||||||
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
|
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
|
||||||
|
@ -173,8 +172,7 @@ private static InsnList serializeValue(Object value) {
|
||||||
return ((Serializer) serializerEntry.getValue()).serialize(value);
|
return ((Serializer) serializerEntry.getValue()).serialize(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
|
throw new UnsupportedOperationException("Serialization of type %s is not supported".formatted(value.getClass()));
|
||||||
value.getClass()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSerializableValue(Object value) {
|
public static boolean isSerializableValue(Object value) {
|
||||||
|
|
|
@ -149,10 +149,7 @@ public static int opcodeEmulation(AbstractInsnNode e) {
|
||||||
break;
|
break;
|
||||||
case INVOKEVIRTUAL:
|
case INVOKEVIRTUAL:
|
||||||
case INVOKESPECIAL:
|
case INVOKESPECIAL:
|
||||||
case INVOKEINTERFACE:
|
case INVOKEINTERFACE, INVOKESTATIC:
|
||||||
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
|
|
||||||
break;
|
|
||||||
case INVOKESTATIC:
|
|
||||||
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
|
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
|
||||||
break;
|
break;
|
||||||
case INVOKEDYNAMIC:
|
case INVOKEDYNAMIC:
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package pro.gravit.launchserver.auth;
|
package pro.gravit.launchserver.auth;
|
||||||
|
|
||||||
import pro.gravit.launcher.events.request.AuthRequestEvent;
|
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Serial;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class AuthException extends IOException {
|
public final class AuthException extends IOException {
|
||||||
|
@Serial
|
||||||
private static final long serialVersionUID = -2586107832847245863L;
|
private static final long serialVersionUID = -2586107832847245863L;
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +16,10 @@ public AuthException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
public static AuthException need2FA() {
|
public static AuthException need2FA() {
|
||||||
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
|
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
import pro.gravit.launchserver.auth.core.MySQLCoreProvider;
|
import pro.gravit.launchserver.auth.mix.MixProvider;
|
||||||
import pro.gravit.launchserver.auth.core.PostgresSQLCoreProvider;
|
|
||||||
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
import pro.gravit.launchserver.auth.texture.TextureProvider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -18,11 +17,12 @@ public final class AuthProviderPair {
|
||||||
public boolean isDefault = true;
|
public boolean isDefault = true;
|
||||||
public AuthCoreProvider core;
|
public AuthCoreProvider core;
|
||||||
public TextureProvider textureProvider;
|
public TextureProvider textureProvider;
|
||||||
|
public Map<String, MixProvider> mixes;
|
||||||
public Map<String, String> links;
|
public Map<String, String> links;
|
||||||
public transient String name;
|
public transient String name;
|
||||||
public transient Set<String> features;
|
public transient Set<String> features;
|
||||||
public String displayName;
|
public String displayName;
|
||||||
private transient boolean warnOAuthShow = false;
|
public boolean visible = true;
|
||||||
|
|
||||||
public AuthProviderPair() {
|
public AuthProviderPair() {
|
||||||
}
|
}
|
||||||
|
@ -38,21 +38,14 @@ public static Set<String> getFeatures(Class<?> clazz) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void internalShowOAuthWarnMessage() {
|
public Set<String> getFeatures() {
|
||||||
if(!warnOAuthShow) {
|
return features;
|
||||||
if(!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
|
|
||||||
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
|
|
||||||
}
|
|
||||||
warnOAuthShow = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void getFeatures(Class<?> clazz, Set<String> list) {
|
public static void getFeatures(Class<?> clazz, Set<String> list) {
|
||||||
Features features = clazz.getAnnotation(Features.class);
|
Feature[] features = clazz.getAnnotationsByType(Feature.class);
|
||||||
if (features != null) {
|
for (Feature feature : features) {
|
||||||
for (Feature feature : features.value()) {
|
list.add(feature.value());
|
||||||
list.add(feature.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
Class<?> superClass = clazz.getSuperclass();
|
||||||
if (superClass != null && superClass != Object.class) {
|
if (superClass != null && superClass != Object.class) {
|
||||||
|
@ -64,39 +57,57 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final <T> T isSupport(Class<T> clazz) {
|
public <T> T isSupport(Class<T> clazz) {
|
||||||
if (core == null) return null;
|
if (core == null) return null;
|
||||||
T result = null;
|
T result = core.isSupport(clazz);
|
||||||
if (result == null) result = core.isSupport(clazz);
|
if (result == null && mixes != null) {
|
||||||
|
for(var m : mixes.values()) {
|
||||||
|
result = m.isSupport(clazz);
|
||||||
|
if(result != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void init(LaunchServer srv, String name) {
|
public void init(LaunchServer srv, String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if (links != null) link(srv);
|
if (links != null) link(srv);
|
||||||
core.init(srv);
|
core.init(srv, this);
|
||||||
features = new HashSet<>();
|
features = new HashSet<>();
|
||||||
getFeatures(core.getClass(), features);
|
getFeatures(core.getClass(), features);
|
||||||
|
if(mixes != null) {
|
||||||
|
for(var m : mixes.values()) {
|
||||||
|
m.init(srv, core);
|
||||||
|
getFeatures(m.getClass(), features);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void link(LaunchServer srv) {
|
public void link(LaunchServer srv) {
|
||||||
links.forEach((k, v) -> {
|
links.forEach((k, v) -> {
|
||||||
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
||||||
if (pair == null) {
|
if (pair == null) {
|
||||||
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
|
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v));
|
||||||
}
|
}
|
||||||
if ("core".equals(k)) {
|
if ("core".equals(k)) {
|
||||||
if (pair.core == null)
|
if (pair.core == null)
|
||||||
throw new NullPointerException(String.format("Auth %s link failed. %s.core is null", name, v));
|
throw new NullPointerException("Auth %s link failed. %s.core is null".formatted(name, v));
|
||||||
core = pair.core;
|
core = pair.core;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void close() throws IOException {
|
public void close() throws IOException {
|
||||||
core.close();
|
core.close();
|
||||||
if (textureProvider != null) {
|
if (textureProvider != null) {
|
||||||
textureProvider.close();
|
textureProvider.close();
|
||||||
}
|
}
|
||||||
|
if(mixes != null) {
|
||||||
|
for(var m : mixes.values()) {
|
||||||
|
m.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package pro.gravit.launchserver.auth;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class HikariSQLSourceConfig implements SQLSourceConfig {
|
||||||
|
private transient volatile HikariDataSource dataSource;
|
||||||
|
private String dsClass;
|
||||||
|
private Properties dsProps;
|
||||||
|
private String driverClass;
|
||||||
|
private String jdbcUrl;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private boolean initializeAtStart;
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if(initializeAtStart) {
|
||||||
|
initializeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeConnection() {
|
||||||
|
if (dataSource != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HikariConfig config = new HikariConfig();
|
||||||
|
consumeIfNotNull(config::setDataSourceClassName, dsClass);
|
||||||
|
consumeIfNotNull(config::setDataSourceProperties, dsProps);
|
||||||
|
consumeIfNotNull(config::setDriverClassName, driverClass);
|
||||||
|
consumeIfNotNull(config::setJdbcUrl, jdbcUrl);
|
||||||
|
consumeIfNotNull(config::setUsername, username);
|
||||||
|
consumeIfNotNull(config::setPassword, password);
|
||||||
|
|
||||||
|
this.dataSource = new HikariDataSource(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
if(dataSource == null && !initializeAtStart) {
|
||||||
|
synchronized (this) {
|
||||||
|
initializeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dataSource.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void consumeIfNotNull(Consumer<T> consumer, T val) {
|
||||||
|
if (val != null) {
|
||||||
|
consumer.accept(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,9 @@
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public final class MySQLSourceConfig implements AutoCloseable {
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
|
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
|
||||||
|
|
||||||
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
||||||
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
|
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
|
||||||
|
@ -33,6 +35,7 @@ public final class MySQLSourceConfig implements AutoCloseable {
|
||||||
private String password;
|
private String password;
|
||||||
private String database;
|
private String database;
|
||||||
private String timezone;
|
private String timezone;
|
||||||
|
private long hikariMaxLifetime = MINUTES.toMillis(30);
|
||||||
private boolean useHikari;
|
private boolean useHikari;
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
|
@ -108,8 +111,8 @@ public synchronized Connection getConnection() throws SQLException {
|
||||||
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
|
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
|
||||||
hikariConfig.setConnectionTestQuery("SELECT 1");
|
hikariConfig.setConnectionTestQuery("SELECT 1");
|
||||||
hikariConfig.setConnectionTimeout(1000);
|
hikariConfig.setConnectionTimeout(1000);
|
||||||
hikariConfig.setAutoCommit(true);
|
|
||||||
hikariConfig.setLeakDetectionThreshold(2000);
|
hikariConfig.setLeakDetectionThreshold(2000);
|
||||||
|
hikariConfig.setMaxLifetime(hikariMaxLifetime);
|
||||||
// Set HikariCP pool
|
// Set HikariCP pool
|
||||||
// Replace source with hds
|
// Replace source with hds
|
||||||
source = new HikariDataSource(hikariConfig);
|
source = new HikariDataSource(hikariConfig);
|
||||||
|
|
|
@ -10,7 +10,10 @@
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public final class PostgreSQLSourceConfig implements AutoCloseable {
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
|
||||||
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
||||||
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
|
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
|
||||||
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
|
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
|
||||||
|
@ -27,9 +30,11 @@ public final class PostgreSQLSourceConfig implements AutoCloseable {
|
||||||
private String password;
|
private String password;
|
||||||
private String database;
|
private String database;
|
||||||
|
|
||||||
|
private long hikariMaxLifetime = MINUTES.toMillis(30); // 30 minutes
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
private DataSource source;
|
private transient DataSource source;
|
||||||
private boolean hikari;
|
private transient boolean hikari;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void close() {
|
public synchronized void close() {
|
||||||
|
@ -65,7 +70,8 @@ public synchronized Connection getConnection() throws SQLException {
|
||||||
hikariSource.setPoolName(poolName);
|
hikariSource.setPoolName(poolName);
|
||||||
hikariSource.setMinimumIdle(0);
|
hikariSource.setMinimumIdle(0);
|
||||||
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
|
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
|
||||||
hikariSource.setIdleTimeout(TIMEOUT * 1000L);
|
hikariSource.setIdleTimeout(SECONDS.toMillis(TIMEOUT));
|
||||||
|
hikariSource.setMaxLifetime(hikariMaxLifetime);
|
||||||
|
|
||||||
// Replace source with hds
|
// Replace source with hds
|
||||||
source = hikariSource;
|
source = hikariSource;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package pro.gravit.launchserver.auth;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public interface SQLSourceConfig {
|
||||||
|
Connection getConnection() throws SQLException;
|
||||||
|
|
||||||
|
void close();
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
|
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
|
||||||
|
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||||
|
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||||
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.HOURS;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
public abstract class AbstractSQLCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
|
||||||
|
public final transient Logger logger = LogManager.getLogger();
|
||||||
|
public long expireSeconds = HOURS.toSeconds(1);
|
||||||
|
public String uuidColumn;
|
||||||
|
public String usernameColumn;
|
||||||
|
public String accessTokenColumn;
|
||||||
|
public String passwordColumn;
|
||||||
|
public String serverIDColumn;
|
||||||
|
public String table;
|
||||||
|
public String permissionsTable;
|
||||||
|
public String permissionsPermissionColumn;
|
||||||
|
public String permissionsUUIDColumn;
|
||||||
|
|
||||||
|
public String rolesTable;
|
||||||
|
public String rolesNameColumn;
|
||||||
|
public String rolesUUIDColumn;
|
||||||
|
|
||||||
|
public PasswordVerifier passwordVerifier;
|
||||||
|
public String customQueryByUUIDSQL;
|
||||||
|
public String customQueryByUsernameSQL;
|
||||||
|
public String customQueryByLoginSQL;
|
||||||
|
public String customQueryPermissionsByUUIDSQL;
|
||||||
|
public String customQueryRolesByUserUUID;
|
||||||
|
public String customUpdateAuthSQL;
|
||||||
|
public String customUpdateServerIdSQL;
|
||||||
|
// Prepared SQL queries
|
||||||
|
public transient String queryByUUIDSQL;
|
||||||
|
public transient String queryByUsernameSQL;
|
||||||
|
public transient String queryByLoginSQL;
|
||||||
|
public transient String queryPermissionsByUUIDSQL;
|
||||||
|
public transient String queryRolesByUserUUID;
|
||||||
|
|
||||||
|
public transient String updateAuthSQL;
|
||||||
|
public transient String updateServerIDSQL;
|
||||||
|
|
||||||
|
public abstract SQLSourceConfig getSQLConfig();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUsername(String username) {
|
||||||
|
try {
|
||||||
|
return queryUser(queryByUsernameSQL, username);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUUID(UUID uuid) {
|
||||||
|
try {
|
||||||
|
return queryUser(queryByUUIDSQL, uuid.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByLogin(String login) {
|
||||||
|
try {
|
||||||
|
return queryUser(queryByLoginSQL, login);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||||
|
try {
|
||||||
|
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
||||||
|
var user = (SQLUser) getUserByUUID(info.uuid());
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return createSession(user);
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
throw new OAuthAccessTokenExpired();
|
||||||
|
} catch (JwtException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||||
|
String[] parts = refreshToken.split("\\.");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String username = parts[0];
|
||||||
|
String token = parts[1];
|
||||||
|
var user = (SQLUser) getUserByUsername(username);
|
||||||
|
if (user == null || user.password == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
||||||
|
if (!token.equals(realToken)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||||
|
return new AuthManager.AuthReport(null, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), createSession(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||||
|
SQLUser user = (SQLUser) getUserByLogin(login);
|
||||||
|
if (user == null) {
|
||||||
|
throw AuthException.userNotFound();
|
||||||
|
}
|
||||||
|
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
||||||
|
if (plainPassword == null) {
|
||||||
|
throw AuthException.wrongPassword();
|
||||||
|
}
|
||||||
|
if (!passwordVerifier.check(user.password, plainPassword.password)) {
|
||||||
|
throw AuthException.wrongPassword();
|
||||||
|
}
|
||||||
|
SQLUserSession session = createSession(user);
|
||||||
|
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||||
|
var refreshToken = user.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(user.username, user.password, server.keyAgreementManager.legacySalt));
|
||||||
|
if (minecraftAccess) {
|
||||||
|
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||||
|
updateAuth(user, minecraftAccessToken);
|
||||||
|
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
|
||||||
|
} else {
|
||||||
|
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
|
||||||
|
SQLUser sqlUser = (SQLUser) user;
|
||||||
|
SQLUserSession session = createSession(sqlUser);
|
||||||
|
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(sqlUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||||
|
var refreshToken = sqlUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(sqlUser.username, sqlUser.password, server.keyAgreementManager.legacySalt));
|
||||||
|
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||||
|
updateAuth(user, minecraftAccessToken);
|
||||||
|
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User checkServer(Client client, String username, String serverID) {
|
||||||
|
SQLUser user = (SQLUser) getUserByUsername(username);
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException {
|
||||||
|
SQLUser user = (SQLUser) client.getUser();
|
||||||
|
if (user == null) return false;
|
||||||
|
return (uuid == null ? user.getUsername().equals(username) : user.getUUID().equals(uuid)) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
|
super.init(server, pair);
|
||||||
|
if (getSQLConfig() == null) logger.error("SQLHolder cannot be null");
|
||||||
|
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
||||||
|
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
||||||
|
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
|
||||||
|
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
|
||||||
|
if (table == null) logger.error("table cannot be null");
|
||||||
|
// Prepare SQL queries
|
||||||
|
String userInfoCols = makeUserCols();
|
||||||
|
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL :
|
||||||
|
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, uuidColumn);
|
||||||
|
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL :
|
||||||
|
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, usernameColumn);
|
||||||
|
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL :
|
||||||
|
"UPDATE %s SET %s=?, %s=NULL WHERE %s=?".formatted(table, accessTokenColumn, serverIDColumn, uuidColumn);
|
||||||
|
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL :
|
||||||
|
"UPDATE %s SET %s=? WHERE %s=?".formatted(table, serverIDColumn, uuidColumn);
|
||||||
|
if (isEnabledPermissions()) {
|
||||||
|
if(isEnabledRoles()) {
|
||||||
|
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
|
||||||
|
"WITH RECURSIVE req AS (\n" +
|
||||||
|
"SELECT p."+permissionsPermissionColumn+" FROM "+permissionsTable+" p WHERE p."+permissionsUUIDColumn+" = ?\n" +
|
||||||
|
"UNION ALL\n" +
|
||||||
|
"SELECT p."+permissionsPermissionColumn+" FROM "+permissionsTable+" p\n" +
|
||||||
|
"INNER JOIN "+rolesTable+" r ON p."+permissionsUUIDColumn+" = r."+rolesUUIDColumn+"\n" +
|
||||||
|
"INNER JOIN req ON r."+rolesUUIDColumn+"=substring(req."+permissionsPermissionColumn+" from 6) or r.name=substring(req."+permissionsPermissionColumn+" from 6)\n" +
|
||||||
|
") SELECT * FROM req";
|
||||||
|
queryRolesByUserUUID = customQueryRolesByUserUUID != null ? customQueryRolesByUserUUID : "SELECT r." + rolesNameColumn + " FROM " + rolesTable + " r\n" +
|
||||||
|
"INNER JOIN " + permissionsTable + " pr ON r." + rolesUUIDColumn + "=substring(pr." + permissionsPermissionColumn + " from 6) or r." + rolesNameColumn + "=substring(pr." + permissionsPermissionColumn + " from 6)\n" +
|
||||||
|
"WHERE pr." + permissionsUUIDColumn + " = ?";
|
||||||
|
} else {
|
||||||
|
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
|
||||||
|
"SELECT (%s) FROM %s WHERE %s=?".formatted(permissionsPermissionColumn, permissionsTable, permissionsUUIDColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String makeUserCols() {
|
||||||
|
return "%s, %s, %s, %s, %s".formatted(uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateAuth(User user, String accessToken) throws IOException {
|
||||||
|
try (Connection c = getSQLConfig().getConnection()) {
|
||||||
|
SQLUser SQLUser = (SQLUser) user;
|
||||||
|
SQLUser.accessToken = accessToken;
|
||||||
|
PreparedStatement s = c.prepareStatement(updateAuthSQL);
|
||||||
|
s.setString(1, accessToken);
|
||||||
|
s.setString(2, user.getUUID().toString());
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
s.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean updateServerID(User user, String serverID) throws IOException {
|
||||||
|
try (Connection c = getSQLConfig().getConnection()) {
|
||||||
|
SQLUser SQLUser = (SQLUser) user;
|
||||||
|
SQLUser.serverId = serverID;
|
||||||
|
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
|
||||||
|
s.setString(1, serverID);
|
||||||
|
s.setString(2, user.getUUID().toString());
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
return s.executeUpdate() > 0;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
getSQLConfig().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SQLUser constructUser(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
||||||
|
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientPermissions requestPermissions (String uuid) throws SQLException
|
||||||
|
{
|
||||||
|
return new ClientPermissions(isEnabledRoles() ? queryRolesNames(queryRolesByUserUUID,uuid) : new ArrayList<>(),
|
||||||
|
isEnabledPermissions() ? queryPermissions(queryPermissionsByUUIDSQL,uuid) : new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SQLUser queryUser(String sql, String value) throws SQLException {
|
||||||
|
SQLUser user;
|
||||||
|
try (Connection c = getSQLConfig().getConnection()) {
|
||||||
|
PreparedStatement s = c.prepareStatement(sql);
|
||||||
|
s.setString(1, value);
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
user = constructUser(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(user != null) {
|
||||||
|
user.permissions = requestPermissions(user.uuid.toString());
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> queryPermissions(String sql, String value) throws SQLException {
|
||||||
|
try (Connection c = getSQLConfig().getConnection()) {
|
||||||
|
PreparedStatement s = c.prepareStatement(sql);
|
||||||
|
s.setString(1, value);
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
ResultSet set = s.executeQuery();
|
||||||
|
List<String> perms = new ArrayList<>();
|
||||||
|
while (set.next())
|
||||||
|
perms.add(set.getString(permissionsPermissionColumn));
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SQLUserSession createSession(SQLUser user) {
|
||||||
|
return new SQLUserSession(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabledPermissions() {
|
||||||
|
return permissionsPermissionColumn != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabledRoles() {
|
||||||
|
return rolesNameColumn != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> queryRolesNames(String sql, String value) throws SQLException {
|
||||||
|
try (Connection c = getSQLConfig().getConnection()) {
|
||||||
|
PreparedStatement s = c.prepareStatement(sql);
|
||||||
|
s.setString(1, value);
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
ResultSet set = s.executeQuery();
|
||||||
|
List<String> perms = new ArrayList<>();
|
||||||
|
while (set.next())
|
||||||
|
perms.add(set.getString(rolesNameColumn));
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SQLUser implements User {
|
||||||
|
protected final UUID uuid;
|
||||||
|
protected final String username;
|
||||||
|
protected String accessToken;
|
||||||
|
protected String serverId;
|
||||||
|
protected final String password;
|
||||||
|
protected ClientPermissions permissions;
|
||||||
|
|
||||||
|
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.username = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.serverId = serverId;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerId() {
|
||||||
|
return serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientPermissions getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SQLUser{" +
|
||||||
|
"uuid=" + uuid +
|
||||||
|
", username='" + username + '\'' +
|
||||||
|
", permissions=" + permissions +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SQLUserSession implements UserSession {
|
||||||
|
private final SQLUser user;
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
public SQLUserSession(SQLUser user) {
|
||||||
|
this.user = user;
|
||||||
|
this.id = user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getID() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMinecraftAccessToken() {
|
||||||
|
return user.getAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getExpireIn() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,20 +3,25 @@
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
|
import pro.gravit.launcher.base.events.RequestEvent;
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
|
||||||
import pro.gravit.launcher.request.auth.details.AuthPasswordDetails;
|
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
|
||||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
import pro.gravit.launcher.base.profiles.PlayerProfile;
|
||||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.base.request.auth.details.AuthPasswordDetails;
|
||||||
|
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
|
||||||
|
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.Reconfigurable;
|
import pro.gravit.launchserver.Reconfigurable;
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportGetAllUsers;
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportGetAllUsers;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration;
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
|
||||||
|
import pro.gravit.launchserver.auth.core.openid.OpenIDAuthCoreProvider;
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
@ -30,6 +35,7 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
All-In-One provider
|
All-In-One provider
|
||||||
|
@ -38,6 +44,8 @@ public abstract class AuthCoreProvider implements AutoCloseable, Reconfigurable
|
||||||
public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider");
|
public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider");
|
||||||
private static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private static boolean registredProviders = false;
|
private static boolean registredProviders = false;
|
||||||
|
protected transient LaunchServer server;
|
||||||
|
protected transient AuthProviderPair pair;
|
||||||
|
|
||||||
public static void registerProviders() {
|
public static void registerProviders() {
|
||||||
if (!registredProviders) {
|
if (!registredProviders) {
|
||||||
|
@ -45,7 +53,9 @@ public static void registerProviders() {
|
||||||
providers.register("mysql", MySQLCoreProvider.class);
|
providers.register("mysql", MySQLCoreProvider.class);
|
||||||
providers.register("postgresql", PostgresSQLCoreProvider.class);
|
providers.register("postgresql", PostgresSQLCoreProvider.class);
|
||||||
providers.register("memory", MemoryAuthCoreProvider.class);
|
providers.register("memory", MemoryAuthCoreProvider.class);
|
||||||
providers.register("http", HttpAuthCoreProvider.class);
|
providers.register("merge", MergeAuthCoreProvider.class);
|
||||||
|
providers.register("openid", OpenIDAuthCoreProvider.class);
|
||||||
|
providers.register("sql", SQLCoreProvider.class);
|
||||||
registredProviders = true;
|
registredProviders = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +82,9 @@ public AuthManager.AuthReport authorize(User user, AuthResponse.AuthContext cont
|
||||||
return authorize(user.getUsername(), context, password, minecraftAccess);
|
return authorize(user.getUsername(), context, password, minecraftAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void init(LaunchServer server);
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
|
this.server = server;
|
||||||
// Auth Handler methods
|
this.pair = pair;
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
|
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
|
||||||
|
@ -91,7 +99,7 @@ public Map<String, Command> getCommands() {
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 1);
|
verifyArgs(args, 1);
|
||||||
AuthRequest.AuthPasswordInterface password = null;
|
AuthRequest.AuthPasswordInterface password = null;
|
||||||
if(args.length > 1) {
|
if (args.length > 1) {
|
||||||
if (args[1].startsWith("{")) {
|
if (args[1].startsWith("{")) {
|
||||||
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
|
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
|
||||||
} else {
|
} else {
|
||||||
|
@ -139,7 +147,7 @@ public void invoke(String... args) throws Exception {
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
map.put("getallusers", new SubCommand("(limit)", "print all users information") {
|
map.put("getallusers", new SubCommand("(limit)", "print all users information") {
|
||||||
@Override
|
@Override
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) {
|
||||||
int max = Integer.MAX_VALUE;
|
int max = Integer.MAX_VALUE;
|
||||||
if (args.length > 0) max = Integer.parseInt(args[0]);
|
if (args.length > 0) max = Integer.parseInt(args[0]);
|
||||||
Iterable<User> users = instance.getAllUsers();
|
Iterable<User> users = instance.getAllUsers();
|
||||||
|
@ -181,28 +189,6 @@ public void invoke(String... args) throws Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
map.put("getuserhardware", new SubCommand("[username]", "get hardware by username") {
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) throws Exception {
|
|
||||||
verifyArgs(args, 1);
|
|
||||||
User user = getUserByUUID(UUID.fromString(args[0]));
|
|
||||||
if (user == null) {
|
|
||||||
logger.info("User {} not found", args[0]);
|
|
||||||
}
|
|
||||||
UserSupportHardware hardware = instance.fetchUserHardware(user);
|
|
||||||
if (hardware == null) {
|
|
||||||
logger.error("Method fetchUserHardware return null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UserHardware userHardware = hardware.getHardware();
|
|
||||||
if (userHardware == null) {
|
|
||||||
logger.info("User {} not contains hardware info", args[0]);
|
|
||||||
} else {
|
|
||||||
logger.info("UserHardware: {}", userHardware);
|
|
||||||
logger.info("HardwareInfo(JSON): {}", Launcher.gsonManager.gson.toJson(userHardware.getHardwareInfo()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") {
|
map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") {
|
||||||
@Override
|
@Override
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) throws Exception {
|
||||||
|
@ -288,25 +274,78 @@ public void invoke(String... args) throws Exception {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
var instance = isSupport(AuthSupportSudo.class);
|
||||||
|
if(instance != null) {
|
||||||
|
map.put("sudo", new SubCommand("[connectUUID] [username/uuid] [isShadow] (CLIENT/API)", "Authorize connectUUID as another user without password") {
|
||||||
|
@Override
|
||||||
|
public void invoke(String... args) throws Exception {
|
||||||
|
verifyArgs(args, 3);
|
||||||
|
UUID connectUUID = UUID.fromString(args[0]);
|
||||||
|
String login = args[1];
|
||||||
|
boolean isShadow = Boolean.parseBoolean(args[2]);
|
||||||
|
AuthResponse.ConnectTypes type;
|
||||||
|
if(args.length > 3) {
|
||||||
|
type = AuthResponse.ConnectTypes.valueOf(args[3]);
|
||||||
|
} else {
|
||||||
|
type = AuthResponse.ConnectTypes.CLIENT;
|
||||||
|
}
|
||||||
|
User user;
|
||||||
|
if(login.length() == 36) {
|
||||||
|
UUID uuid = UUID.fromString(login);
|
||||||
|
user = getUserByUUID(uuid);
|
||||||
|
} else {
|
||||||
|
user = getUserByUsername(login);
|
||||||
|
}
|
||||||
|
if(user == null) {
|
||||||
|
logger.error("User {} not found", login);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AtomicBoolean founded = new AtomicBoolean();
|
||||||
|
server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, fh) -> {
|
||||||
|
var client = fh.getClient();
|
||||||
|
if(client == null || !connectUUID.equals(fh.getConnectUUID())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info("Found connectUUID {} with IP {}", fh.getConnectUUID(), fh.context == null ? "null" : fh.context.ip);
|
||||||
|
var lock = server.config.netty.performance.disableThreadSafeClientObject ? null : client.writeLock();
|
||||||
|
if(lock != null) {
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var report = instance.sudo(user, isShadow);
|
||||||
|
User user1 = report.session().getUser();
|
||||||
|
server.authManager.internalAuth(client, type, pair, user1.getUsername(), user1.getUUID(), user1.getPermissions(), true);
|
||||||
|
client.sessionObject = report.session();
|
||||||
|
client.coreObject = report.session().getUser();
|
||||||
|
PlayerProfile playerProfile = server.authManager.getPlayerProfile(client);
|
||||||
|
AuthRequestEvent request = new AuthRequestEvent(user1.getPermissions(), playerProfile,
|
||||||
|
report.minecraftAccessToken(), null, null,
|
||||||
|
new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()));
|
||||||
|
request.requestUUID = RequestEvent.eventUUID;
|
||||||
|
server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, request);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("Sudo error", e);
|
||||||
|
} finally {
|
||||||
|
if(lock != null) {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
founded.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!founded.get()) {
|
||||||
|
logger.error("ConnectUUID {} not found", connectUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User checkServer(Client client, String username, String serverID) throws IOException {
|
public abstract User checkServer(Client client, String username, String serverID) throws IOException;
|
||||||
User user = getUserByUsername(username);
|
|
||||||
if (user == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
|
public abstract boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException;
|
||||||
User user = client.getUser();
|
|
||||||
if (user == null) return false;
|
|
||||||
return user.getUsername().equals(username) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T isSupport(Class<T> clazz) {
|
public <T> T isSupport(Class<T> clazz) {
|
||||||
|
@ -315,7 +354,7 @@ public <T> T isSupport(Class<T> clazz) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void close() throws IOException;
|
public abstract void close();
|
||||||
|
|
||||||
public static class PasswordVerifyReport {
|
public static class PasswordVerifyReport {
|
||||||
public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1);
|
public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1);
|
||||||
|
|
|
@ -1,419 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
|
||||||
import pro.gravit.launcher.events.request.AuthRequestEvent;
|
|
||||||
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
|
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
|
||||||
import pro.gravit.launchserver.HttpRequester;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties;
|
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
|
|
||||||
import pro.gravit.launchserver.helper.HttpHelper;
|
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class HttpAuthCoreProvider extends AuthCoreProvider {
|
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
|
||||||
private transient HttpRequester requester;
|
|
||||||
public String bearerToken;
|
|
||||||
public String getUserByUsernameUrl;
|
|
||||||
public String getUserByLoginUrl;
|
|
||||||
public String getUserByUUIDUrl;
|
|
||||||
public String getUserByTokenUrl;
|
|
||||||
public String getAuthDetails;
|
|
||||||
public String refreshTokenUrl;
|
|
||||||
public String authorizeUrl;
|
|
||||||
public String joinServerUrl;
|
|
||||||
public String checkServerUrl;
|
|
||||||
public String updateServerIdUrl;
|
|
||||||
@Override
|
|
||||||
public User getUserByUsername(String username) {
|
|
||||||
try {
|
|
||||||
return requester.send(requester.get(CommonHelper.replace(getUserByUsernameUrl, "username", username), null), HttpUser.class).getOrThrow();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUserByLogin(String login) {
|
|
||||||
if(getUserByLoginUrl != null) {
|
|
||||||
try {
|
|
||||||
return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getUserByLogin(login);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUserByUUID(UUID uuid) {
|
|
||||||
try {
|
|
||||||
return requester.send(requester.get(CommonHelper.replace(getUserByUUIDUrl, "uuid", uuid.toString()), null), HttpUser.class).getOrThrow();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
|
|
||||||
if(getAuthDetails == null) {
|
|
||||||
return super.getDetails(client);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var result = requester.send(requester.get(getAuthDetails, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
|
|
||||||
return result.details;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return super.getDetails(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
|
||||||
if(getUserByTokenUrl == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class);
|
|
||||||
if(!result.isSuccessful()) {
|
|
||||||
var error = result.error().error;
|
|
||||||
if(error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) {
|
|
||||||
throw new OAuthAccessTokenExpired();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return result.getOrThrow();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
|
||||||
if(refreshTokenUrl == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return requester.send(requester.post(refreshTokenUrl, new RefreshTokenRequest(refreshToken, context),
|
|
||||||
null), AuthManager.AuthReport.class).getOrThrow();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
|
||||||
var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess),
|
|
||||||
bearerToken), HttpAuthReport.class);
|
|
||||||
if(!result.isSuccessful()) {
|
|
||||||
var error = result.error().error;
|
|
||||||
if(error != null) {
|
|
||||||
throw new AuthException(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.getOrThrow().toAuthReport();
|
|
||||||
}
|
|
||||||
|
|
||||||
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
|
|
||||||
String oauthRefreshToken, long oauthExpire,
|
|
||||||
HttpUserSession session) {
|
|
||||||
public AuthManager.AuthReport toAuthReport() {
|
|
||||||
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
|
||||||
var result = requester.send(requester.post(updateServerIdUrl, new UpdateServerIdRequest(user.getUsername(), user.getUUID(), serverID),
|
|
||||||
null), Void.class);
|
|
||||||
return result.isSuccessful();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User checkServer(Client client, String username, String serverID) throws IOException {
|
|
||||||
return requester.send(requester.post(checkServerUrl, new CheckServerRequest(username, serverID), bearerToken), HttpUser.class).getOrThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
|
|
||||||
var result = requester.send(requester.post(joinServerUrl, new JoinServerRequest(username, accessToken, serverID), bearerToken), Void.class);
|
|
||||||
return result.isSuccessful();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class UpdateServerIdRequest {
|
|
||||||
public String username;
|
|
||||||
public UUID uuid;
|
|
||||||
public String serverId;
|
|
||||||
|
|
||||||
public UpdateServerIdRequest(String username, UUID uuid, String serverId) {
|
|
||||||
this.username = username;
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.serverId = serverId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CheckServerRequest {
|
|
||||||
public String username;
|
|
||||||
public String serverId;
|
|
||||||
|
|
||||||
public CheckServerRequest(String username, String serverId) {
|
|
||||||
this.username = username;
|
|
||||||
this.serverId = serverId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GetAuthDetailsResponse {
|
|
||||||
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JoinServerRequest {
|
|
||||||
public String username;
|
|
||||||
public String accessToken;
|
|
||||||
public String serverId;
|
|
||||||
|
|
||||||
public JoinServerRequest(String username, String accessToken, String serverId) {
|
|
||||||
this.username = username;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.serverId = serverId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(LaunchServer server) {
|
|
||||||
requester = new HttpRequester();
|
|
||||||
if(getUserByUsernameUrl == null) {
|
|
||||||
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
|
|
||||||
}
|
|
||||||
if(getUserByUUIDUrl == null) {
|
|
||||||
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
|
|
||||||
}
|
|
||||||
if(authorizeUrl == null) {
|
|
||||||
throw new IllegalArgumentException("'authorizeUrl' can't be null");
|
|
||||||
}
|
|
||||||
if(checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
|
|
||||||
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AuthorizeRequest {
|
|
||||||
public String login;
|
|
||||||
public AuthResponse.AuthContext context;
|
|
||||||
public AuthRequest.AuthPasswordInterface password;
|
|
||||||
public boolean minecraftAccess;
|
|
||||||
|
|
||||||
public AuthorizeRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorizeRequest(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) {
|
|
||||||
this.login = login;
|
|
||||||
this.context = context;
|
|
||||||
this.password = password;
|
|
||||||
this.minecraftAccess = minecraftAccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RefreshTokenRequest {
|
|
||||||
public String refreshToken;
|
|
||||||
public AuthResponse.AuthContext context;
|
|
||||||
|
|
||||||
public RefreshTokenRequest(String refreshToken, AuthResponse.AuthContext context) {
|
|
||||||
this.refreshToken = refreshToken;
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class HttpUser implements User, UserSupportTextures, UserSupportProperties {
|
|
||||||
private String username;
|
|
||||||
private UUID uuid;
|
|
||||||
private String serverId;
|
|
||||||
private String accessToken;
|
|
||||||
private ClientPermissions permissions;
|
|
||||||
@Deprecated
|
|
||||||
private Texture skin;
|
|
||||||
@Deprecated
|
|
||||||
private Texture cloak;
|
|
||||||
private Map<String, Texture> assets;
|
|
||||||
private Map<String, String> properties;
|
|
||||||
|
|
||||||
public HttpUser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions) {
|
|
||||||
this.username = username;
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.permissions = permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak) {
|
|
||||||
this.username = username;
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.permissions = permissions;
|
|
||||||
this.skin = skin;
|
|
||||||
this.cloak = cloak;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, Map<String, String> properties) {
|
|
||||||
this.username = username;
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.permissions = permissions;
|
|
||||||
this.skin = skin;
|
|
||||||
this.cloak = cloak;
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Map<String, Texture> assets, Map<String, String> properties) {
|
|
||||||
this.username = username;
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.permissions = permissions;
|
|
||||||
this.assets = assets;
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UUID getUUID() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServerId() {
|
|
||||||
return serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientPermissions getPermissions() {
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Texture getSkinTexture() {
|
|
||||||
if(assets == null) {
|
|
||||||
return skin;
|
|
||||||
}
|
|
||||||
return assets.get("SKIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Texture getCloakTexture() {
|
|
||||||
if(assets == null) {
|
|
||||||
return cloak;
|
|
||||||
}
|
|
||||||
return assets.get("CAPE");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Texture> getAssets() {
|
|
||||||
if(assets == null) {
|
|
||||||
Map<String, Texture> map = new HashMap<>();
|
|
||||||
if(skin != null) {
|
|
||||||
map.put("SKIN", skin);
|
|
||||||
}
|
|
||||||
if(cloak != null) {
|
|
||||||
map.put("CAPE", cloak);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
return assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getProperties() {
|
|
||||||
if(properties == null) {
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "HttpUser{" +
|
|
||||||
"username='" + username + '\'' +
|
|
||||||
", uuid=" + uuid +
|
|
||||||
", serverId='" + serverId + '\'' +
|
|
||||||
", accessToken='" + accessToken + '\'' +
|
|
||||||
", permissions=" + permissions +
|
|
||||||
", assets=" + getAssets() +
|
|
||||||
", properties=" + properties +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class HttpUserSession implements UserSession {
|
|
||||||
private String id;
|
|
||||||
private HttpUser user;
|
|
||||||
private long expireIn;
|
|
||||||
|
|
||||||
public HttpUserSession() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpUserSession(String id, HttpUser user, long expireIn) {
|
|
||||||
this.id = id;
|
|
||||||
this.user = user;
|
|
||||||
this.expireIn = expireIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getID() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getExpireIn() {
|
|
||||||
return expireIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "HttpUserSession{" +
|
|
||||||
"id='" + id + '\'' +
|
|
||||||
", user=" + user +
|
|
||||||
", expireIn=" + expireIn +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
|
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
import pro.gravit.launcher.request.auth.details.AuthLoginOnlyDetails;
|
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
@ -13,15 +13,19 @@
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class MemoryAuthCoreProvider extends AuthCoreProvider {
|
public class MemoryAuthCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
|
||||||
private transient final List<MemoryUser> memory = new ArrayList<>(16);
|
private transient final List<MemoryUser> memory = new ArrayList<>(16);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUsername(String username) {
|
public User getUserByUsername(String username) {
|
||||||
synchronized (memory) {
|
synchronized (memory) {
|
||||||
for(MemoryUser u : memory) {
|
for (MemoryUser u : memory) {
|
||||||
if(u.username.equals(username)) {
|
if (u.username.equals(username)) {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +43,8 @@ public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUUID(UUID uuid) {
|
public User getUserByUUID(UUID uuid) {
|
||||||
synchronized (memory) {
|
synchronized (memory) {
|
||||||
for(MemoryUser u : memory) {
|
for (MemoryUser u : memory) {
|
||||||
if(u.uuid.equals(uuid)) {
|
if (u.uuid.equals(uuid)) {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,10 +53,10 @@ public User getUserByUUID(UUID uuid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
|
||||||
synchronized (memory) {
|
synchronized (memory) {
|
||||||
for(MemoryUser u : memory) {
|
for (MemoryUser u : memory) {
|
||||||
if(u.accessToken.equals(accessToken)) {
|
if (u.accessToken.equals(accessToken)) {
|
||||||
return new MemoryUserSession(u);
|
return new MemoryUserSession(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,23 +71,23 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||||
if(login == null) {
|
if (login == null) {
|
||||||
throw AuthException.userNotFound();
|
throw AuthException.userNotFound();
|
||||||
}
|
}
|
||||||
MemoryUser user = null;
|
MemoryUser user = null;
|
||||||
synchronized (memory) {
|
synchronized (memory) {
|
||||||
for(MemoryUser u : memory) {
|
for (MemoryUser u : memory) {
|
||||||
if(u.username.equals(login)) {
|
if (u.username.equals(login)) {
|
||||||
user = u;
|
user = u;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(user == null) {
|
if (user == null) {
|
||||||
user = new MemoryUser(login);
|
user = new MemoryUser(login);
|
||||||
memory.add(user);
|
memory.add(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!minecraftAccess) {
|
if (!minecraftAccess) {
|
||||||
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
|
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
|
||||||
} else {
|
} else {
|
||||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
|
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
|
||||||
|
@ -91,17 +95,10 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
public User checkServer(Client client, String username, String serverID) {
|
||||||
MemoryUser memoryUser = (MemoryUser) user;
|
|
||||||
memoryUser.serverId = serverID;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User checkServer(Client client, String username, String serverID) throws IOException {
|
|
||||||
synchronized (memory) {
|
synchronized (memory) {
|
||||||
for(MemoryUser u : memory) {
|
for (MemoryUser u : memory) {
|
||||||
if(u.username.equals(username)) {
|
if (u.username.equals(username)) {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,26 +109,26 @@ public User checkServer(Client client, String username, String serverID) throws
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
|
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(LaunchServer server) {
|
public void close() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
|
||||||
|
return authorize(user.getUsername(), null, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MemoryUser implements User {
|
public static class MemoryUser implements User {
|
||||||
private String username;
|
private final String username;
|
||||||
private UUID uuid;
|
private final UUID uuid;
|
||||||
private String serverId;
|
private String serverId;
|
||||||
private String accessToken;
|
private final String accessToken;
|
||||||
private ClientPermissions permissions;
|
private final ClientPermissions permissions;
|
||||||
|
|
||||||
public MemoryUser(String username) {
|
public MemoryUser(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
@ -154,16 +151,6 @@ public UUID getUUID() {
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServerId() {
|
|
||||||
return serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientPermissions getPermissions() {
|
public ClientPermissions getPermissions() {
|
||||||
return permissions;
|
return permissions;
|
||||||
|
@ -184,9 +171,9 @@ public int hashCode() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MemoryUserSession implements UserSession {
|
public static class MemoryUserSession implements UserSession {
|
||||||
private String id;
|
private final String id;
|
||||||
private MemoryUser user;
|
private final MemoryUser user;
|
||||||
private long expireIn;
|
private final long expireIn;
|
||||||
|
|
||||||
public MemoryUserSession(MemoryUser user) {
|
public MemoryUserSession(MemoryUser user) {
|
||||||
this.id = SecurityHelper.randomStringToken();
|
this.id = SecurityHelper.randomStringToken();
|
||||||
|
@ -204,6 +191,11 @@ public User getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMinecraftAccessToken() {
|
||||||
|
return "IGNORED";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getExpireIn() {
|
public long getExpireIn() {
|
||||||
return expireIn;
|
return expireIn;
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class MergeAuthCoreProvider extends AuthCoreProvider {
|
||||||
|
private transient final Logger logger = LogManager.getLogger(MergeAuthCoreProvider.class);
|
||||||
|
public List<String> list = new ArrayList<>();
|
||||||
|
private final transient List<AuthCoreProvider> providers = new ArrayList<>();
|
||||||
|
@Override
|
||||||
|
public User getUserByUsername(String username) {
|
||||||
|
for(var core : providers) {
|
||||||
|
var result = core.getUserByUsername(username);
|
||||||
|
if(result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUUID(UUID uuid) {
|
||||||
|
for(var core : providers) {
|
||||||
|
var result = core.getUserByUUID(uuid);
|
||||||
|
if(result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||||
|
throw new OAuthAccessTokenExpired(); // Authorization not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||||
|
throw new AuthException("Authorization not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User checkServer(Client client, String username, String serverID) throws IOException {
|
||||||
|
for(var core : providers) {
|
||||||
|
var result = core.checkServer(client, username, serverID);
|
||||||
|
if(result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
|
||||||
|
return false; // Authorization not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server, AuthProviderPair pair1) {
|
||||||
|
for(var e : list) {
|
||||||
|
var pair = server.config.auth.get(e);
|
||||||
|
if(pair != null) {
|
||||||
|
providers.add(pair.core);
|
||||||
|
} else {
|
||||||
|
logger.warn("Provider {} not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Providers closed automatically
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,59 +1,30 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||||
import io.jsonwebtoken.JwtException;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
|
||||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
|
||||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
|
||||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
|
||||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
|
public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware {
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
|
||||||
public MySQLSourceConfig mySQLHolder;
|
public MySQLSourceConfig mySQLHolder;
|
||||||
|
|
||||||
public int expireSeconds = 3600;
|
|
||||||
public String uuidColumn;
|
|
||||||
public String usernameColumn;
|
|
||||||
public String accessTokenColumn;
|
|
||||||
public String passwordColumn;
|
|
||||||
public String serverIDColumn;
|
|
||||||
public String hardwareIdColumn;
|
public String hardwareIdColumn;
|
||||||
public String table;
|
|
||||||
|
|
||||||
public String tableHWID = "hwids";
|
public String tableHWID = "hwids";
|
||||||
public String tableHWIDLog = "hwidLog";
|
public String tableHWIDLog = "hwidLog";
|
||||||
public PasswordVerifier passwordVerifier;
|
|
||||||
public double criticalCompareLevel = 1.0;
|
public double criticalCompareLevel = 1.0;
|
||||||
public String customQueryByUUIDSQL;
|
|
||||||
public String customQueryByUsernameSQL;
|
|
||||||
public String customQueryByLoginSQL;
|
|
||||||
public String customUpdateAuthSQL;
|
|
||||||
public String customUpdateServerIdSQL;
|
|
||||||
private transient String sqlFindHardwareByPublicKey;
|
private transient String sqlFindHardwareByPublicKey;
|
||||||
private transient String sqlFindHardwareByData;
|
private transient String sqlFindHardwareByData;
|
||||||
private transient String sqlFindHardwareById;
|
private transient String sqlFindHardwareById;
|
||||||
|
@ -63,186 +34,45 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
|
||||||
private transient String sqlUpdateHardwareBanned;
|
private transient String sqlUpdateHardwareBanned;
|
||||||
private transient String sqlUpdateUsers;
|
private transient String sqlUpdateUsers;
|
||||||
private transient String sqlUsersByHwidId;
|
private transient String sqlUsersByHwidId;
|
||||||
// Prepared SQL queries
|
|
||||||
private transient String queryByUUIDSQL;
|
|
||||||
private transient String queryByUsernameSQL;
|
|
||||||
private transient String queryByLoginSQL;
|
|
||||||
private transient String updateAuthSQL;
|
|
||||||
private transient String updateServerIDSQL;
|
|
||||||
|
|
||||||
private transient LaunchServer server;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUsername(String username) {
|
public SQLSourceConfig getSQLConfig() {
|
||||||
try {
|
return mySQLHolder;
|
||||||
return query(queryByUsernameSQL, username);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUUID(UUID uuid) {
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
try {
|
super.init(server, pair);
|
||||||
return query(queryByUUIDSQL, uuid.toString());
|
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
|
||||||
} catch (IOException e) {
|
String userInfoCols = makeUserCols();
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUserByLogin(String login) {
|
|
||||||
try {
|
|
||||||
return query(queryByLoginSQL, login);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
|
||||||
try {
|
|
||||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
|
||||||
var user = (MySQLUser) getUserByUUID(info.uuid());
|
|
||||||
if(user == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new MySQLUserSession(user);
|
|
||||||
} catch (ExpiredJwtException e) {
|
|
||||||
throw new OAuthAccessTokenExpired();
|
|
||||||
} catch (JwtException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
|
||||||
String[] parts = refreshToken.split("\\.");
|
|
||||||
if(parts.length != 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String username = parts[0];
|
|
||||||
String token = parts[1];
|
|
||||||
var user = (MySQLUser) getUserByUsername(username);
|
|
||||||
if(user == null || user.password == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
|
||||||
if(!token.equals(realToken)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
|
||||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
|
||||||
MySQLUser mySQLUser = (MySQLUser) getUserByLogin(login);
|
|
||||||
if(mySQLUser == null) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
if(context != null) {
|
|
||||||
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
|
||||||
if(plainPassword == null) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
if(!passwordVerifier.check(mySQLUser.password, plainPassword.password)) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MySQLUserSession session = new MySQLUserSession(mySQLUser);
|
|
||||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
|
||||||
var refreshToken = mySQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt));
|
|
||||||
if (minecraftAccess) {
|
|
||||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
|
||||||
updateAuth(mySQLUser, minecraftAccessToken);
|
|
||||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
|
||||||
} else {
|
|
||||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(LaunchServer server) {
|
|
||||||
this.server = server;
|
|
||||||
if (mySQLHolder == null) logger.error("mySQLHolder cannot be null");
|
|
||||||
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
|
||||||
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
|
||||||
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
|
|
||||||
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
|
|
||||||
if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null");
|
|
||||||
if (table == null) logger.error("table cannot be null");
|
|
||||||
// Prepare SQL queries
|
|
||||||
String userInfoCols = String.format("%s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, hardwareIdColumn);
|
|
||||||
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
|
|
||||||
table, uuidColumn);
|
|
||||||
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
|
|
||||||
userInfoCols, table, usernameColumn);
|
|
||||||
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
|
||||||
|
|
||||||
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
|
|
||||||
table, accessTokenColumn, serverIDColumn, uuidColumn);
|
|
||||||
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
|
|
||||||
table, serverIDColumn, uuidColumn);
|
|
||||||
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
|
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
|
||||||
if (sqlFindHardwareByPublicKey == null)
|
if (sqlFindHardwareByPublicKey == null)
|
||||||
sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID);
|
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE `publicKey` = ?".formatted(hardwareInfoCols, tableHWID);
|
||||||
if (sqlFindHardwareById == null)
|
if (sqlFindHardwareById == null)
|
||||||
sqlFindHardwareById = String.format("SELECT %s FROM %s WHERE `id` = ?", hardwareInfoCols, tableHWID);
|
sqlFindHardwareById = "SELECT %s FROM %s WHERE `id` = ?".formatted(hardwareInfoCols, tableHWID);
|
||||||
if (sqlUsersByHwidId == null)
|
if (sqlUsersByHwidId == null)
|
||||||
sqlUsersByHwidId = String.format("SELECT %s FROM %s WHERE `%s` = ?", userInfoCols, table, hardwareIdColumn);
|
sqlUsersByHwidId = "SELECT %s FROM %s WHERE `%s` = ?".formatted(userInfoCols, table, hardwareIdColumn);
|
||||||
if (sqlFindHardwareByData == null)
|
if (sqlFindHardwareByData == null)
|
||||||
sqlFindHardwareByData = String.format("SELECT %s FROM %s", hardwareInfoCols, tableHWID);
|
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
|
||||||
if (sqlCreateHardware == null)
|
if (sqlCreateHardware == null)
|
||||||
sqlCreateHardware = String.format("INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')", tableHWID);
|
sqlCreateHardware = "INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
|
||||||
if (sqlCreateHWIDLog == null)
|
if (sqlCreateHWIDLog == null)
|
||||||
sqlCreateHWIDLog = String.format("INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)", tableHWIDLog);
|
sqlCreateHWIDLog = "INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)".formatted(tableHWIDLog);
|
||||||
if (sqlUpdateHardwarePublicKey == null)
|
if (sqlUpdateHardwarePublicKey == null)
|
||||||
sqlUpdateHardwarePublicKey = String.format("UPDATE %s SET `publicKey` = ? WHERE `id` = ?", tableHWID);
|
sqlUpdateHardwarePublicKey = "UPDATE %s SET `publicKey` = ? WHERE `id` = ?".formatted(tableHWID);
|
||||||
sqlUpdateHardwareBanned = String.format("UPDATE %s SET `banned` = ? WHERE `id` = ?", tableHWID);
|
sqlUpdateHardwareBanned = "UPDATE %s SET `banned` = ? WHERE `id` = ?".formatted(tableHWID);
|
||||||
sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
|
sqlUpdateUsers = "UPDATE %s SET `%s` = ? WHERE `%s` = ?".formatted(table, hardwareIdColumn, uuidColumn);
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean updateAuth(User user, String accessToken) throws IOException {
|
|
||||||
try (Connection c = mySQLHolder.getConnection()) {
|
|
||||||
MySQLUser mySQLUser = (MySQLUser) user;
|
|
||||||
mySQLUser.accessToken = accessToken;
|
|
||||||
PreparedStatement s = c.prepareStatement(updateAuthSQL);
|
|
||||||
s.setString(1, accessToken);
|
|
||||||
s.setString(2, user.getUUID().toString());
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
return s.executeUpdate() > 0;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
protected String makeUserCols() {
|
||||||
try (Connection c = mySQLHolder.getConnection()) {
|
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
|
||||||
MySQLUser mySQLUser = (MySQLUser) user;
|
|
||||||
mySQLUser.serverId = serverID;
|
|
||||||
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
|
|
||||||
s.setString(1, serverID);
|
|
||||||
s.setString(2, user.getUUID().toString());
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
return s.executeUpdate() > 0;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
protected MySQLUser constructUser(ResultSet set) throws SQLException {
|
||||||
mySQLHolder.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MySQLUser constructUser(ResultSet set) throws SQLException {
|
|
||||||
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
||||||
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions(), set.getLong(hardwareIdColumn)) : null;
|
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
|
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
|
||||||
|
@ -271,19 +101,6 @@ private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) th
|
||||||
s.executeUpdate();
|
s.executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private User query(String sql, String value) throws IOException {
|
|
||||||
try (Connection c = mySQLHolder.getConnection()) {
|
|
||||||
PreparedStatement s = c.prepareStatement(sql);
|
|
||||||
s.setString(1, value);
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
try (ResultSet set = s.executeQuery()) {
|
|
||||||
return constructUser(set);
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
|
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
|
||||||
try (Connection connection = mySQLHolder.getConnection()) {
|
try (Connection connection = mySQLHolder.getConnection()) {
|
||||||
|
@ -371,8 +188,8 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
|
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
|
||||||
MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession;
|
SQLUserSession mySQLUserSession = (SQLUserSession) userSession;
|
||||||
MySQLUser mySQLUser = mySQLUserSession.user;
|
MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser();
|
||||||
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
|
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
|
||||||
if (mySQLUser.hwidId == mySQLUserHardware.id) return;
|
if (mySQLUser.hwidId == mySQLUserHardware.id) return;
|
||||||
mySQLUser.hwidId = mySQLUserHardware.id;
|
mySQLUser.hwidId = mySQLUserHardware.id;
|
||||||
|
@ -444,6 +261,34 @@ public void unbanHardware(UserHardware hardware) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SQLUserSession createSession(SQLUser user) {
|
||||||
|
return new MySQLUserSession(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MySQLUserSession extends SQLUserSession implements UserSessionSupportHardware {
|
||||||
|
private transient MySQLUser mySQLUser;
|
||||||
|
protected transient MySQLUserHardware hardware;
|
||||||
|
|
||||||
|
public MySQLUserSession(SQLUser user) {
|
||||||
|
super(user);
|
||||||
|
mySQLUser = (MySQLUser) user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHardwareId() {
|
||||||
|
return mySQLUser.hwidId == 0 ? null : String.valueOf(mySQLUser.hwidId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware getHardware() {
|
||||||
|
if(hardware == null) {
|
||||||
|
hardware = (MySQLUserHardware) getHardwareInfoById(String.valueOf(mySQLUser.hwidId));
|
||||||
|
}
|
||||||
|
return hardware;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class MySQLUserHardware implements UserHardware {
|
public static class MySQLUserHardware implements UserHardware {
|
||||||
private final HardwareReportRequest.HardwareInfo hardwareInfo;
|
private final HardwareReportRequest.HardwareInfo hardwareInfo;
|
||||||
private final long id;
|
private final long id;
|
||||||
|
@ -488,59 +333,14 @@ public String toString() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MySQLUser implements User, UserSupportHardware {
|
public static class MySQLUser extends SQLUser {
|
||||||
protected UUID uuid;
|
|
||||||
protected String username;
|
|
||||||
protected String accessToken;
|
|
||||||
protected String serverId;
|
|
||||||
protected String password;
|
|
||||||
protected ClientPermissions permissions;
|
|
||||||
protected long hwidId;
|
protected long hwidId;
|
||||||
protected transient MySQLUserHardware hardware;
|
|
||||||
|
|
||||||
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
|
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
|
||||||
this.uuid = uuid;
|
super(uuid, username, accessToken, serverId, password);
|
||||||
this.username = username;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.password = password;
|
|
||||||
this.permissions = permissions;
|
|
||||||
this.hwidId = hwidId;
|
this.hwidId = hwidId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UUID getUUID() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServerId() {
|
|
||||||
return serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientPermissions getPermissions() {
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserHardware getHardware() {
|
|
||||||
if (hardware != null) return hardware;
|
|
||||||
MySQLUserHardware result = (MySQLUserHardware) getHardwareInfoById(String.valueOf(hwidId));
|
|
||||||
hardware = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MySQLUser{" +
|
return "MySQLUser{" +
|
||||||
|
@ -551,29 +351,4 @@ public String toString() {
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MySQLUserSession implements UserSession {
|
|
||||||
private final MySQLUser user;
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
public MySQLUserSession(MySQLUser user) {
|
|
||||||
this.user = user;
|
|
||||||
this.id = user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getID() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getExpireIn() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,297 +1,21 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
|
||||||
import io.jsonwebtoken.JwtException;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
|
||||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
|
||||||
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
|
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
|
||||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
|
||||||
import java.sql.*;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class PostgresSQLCoreProvider extends AuthCoreProvider {
|
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
|
||||||
public PostgreSQLSourceConfig postgresSQLHolder;
|
public PostgreSQLSourceConfig postgresSQLHolder;
|
||||||
|
|
||||||
public int expireSeconds = 3600;
|
|
||||||
public String uuidColumn;
|
|
||||||
public String usernameColumn;
|
|
||||||
public String accessTokenColumn;
|
|
||||||
public String passwordColumn;
|
|
||||||
public String serverIDColumn;
|
|
||||||
public String table;
|
|
||||||
|
|
||||||
public PasswordVerifier passwordVerifier;
|
|
||||||
public String customQueryByUUIDSQL;
|
|
||||||
public String customQueryByUsernameSQL;
|
|
||||||
public String customQueryByLoginSQL;
|
|
||||||
public String customUpdateAuthSQL;
|
|
||||||
public String customUpdateServerIdSQL;
|
|
||||||
// Prepared SQL queries
|
|
||||||
private transient String queryByUUIDSQL;
|
|
||||||
private transient String queryByUsernameSQL;
|
|
||||||
private transient String queryByLoginSQL;
|
|
||||||
private transient String updateAuthSQL;
|
|
||||||
private transient String updateServerIDSQL;
|
|
||||||
|
|
||||||
private transient LaunchServer server;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUsername(String username) {
|
public SQLSourceConfig getSQLConfig() {
|
||||||
try {
|
return postgresSQLHolder;
|
||||||
return query(queryByUsernameSQL, username);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public User getUserByUUID(UUID uuid) {
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
try {
|
super.init(server, pair);
|
||||||
return query(queryByUUIDSQL, uuid.toString());
|
logger.warn("Method 'postgresql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUserByLogin(String login) {
|
|
||||||
try {
|
|
||||||
return query(queryByLoginSQL, login);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("SQL error", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
|
||||||
try {
|
|
||||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
|
||||||
var user = (PostgresSQLUser) getUserByUUID(info.uuid());
|
|
||||||
if(user == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new PostgresSQLCoreProvider.MySQLUserSession(user);
|
|
||||||
} catch (ExpiredJwtException e) {
|
|
||||||
throw new OAuthAccessTokenExpired();
|
|
||||||
} catch (JwtException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
|
||||||
String[] parts = refreshToken.split("\\.");
|
|
||||||
if(parts.length != 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String username = parts[0];
|
|
||||||
String token = parts[1];
|
|
||||||
var user = (PostgresSQLUser) getUserByUsername(username);
|
|
||||||
if(user == null || user.password == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
|
||||||
if(!token.equals(realToken)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
|
||||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new PostgresSQLCoreProvider.MySQLUserSession(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
|
||||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) getUserByLogin(login);
|
|
||||||
if(postgresSQLUser == null) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
if(context != null) {
|
|
||||||
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
|
||||||
if(plainPassword == null) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
if(!passwordVerifier.check(postgresSQLUser.password, plainPassword.password)) {
|
|
||||||
throw AuthException.wrongPassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MySQLUserSession session = new MySQLUserSession(postgresSQLUser);
|
|
||||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
|
||||||
var refreshToken = postgresSQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt));
|
|
||||||
if (minecraftAccess) {
|
|
||||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
|
||||||
updateAuth(postgresSQLUser, minecraftAccessToken);
|
|
||||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
|
||||||
} else {
|
|
||||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(LaunchServer server) {
|
|
||||||
this.server = server;
|
|
||||||
if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null");
|
|
||||||
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
|
||||||
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
|
||||||
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
|
|
||||||
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
|
|
||||||
if (table == null) logger.error("table cannot be null");
|
|
||||||
// Prepare SQL queries
|
|
||||||
String userInfoCols = String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
|
|
||||||
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
|
|
||||||
table, uuidColumn);
|
|
||||||
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
|
|
||||||
userInfoCols, table, usernameColumn);
|
|
||||||
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
|
||||||
|
|
||||||
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
|
|
||||||
table, accessTokenColumn, serverIDColumn, uuidColumn);
|
|
||||||
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
|
|
||||||
table, serverIDColumn, uuidColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean updateAuth(User user, String accessToken) throws IOException {
|
|
||||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
|
||||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
|
|
||||||
postgresSQLUser.accessToken = accessToken;
|
|
||||||
PreparedStatement s = c.prepareStatement(updateAuthSQL);
|
|
||||||
s.setString(1, accessToken);
|
|
||||||
s.setString(2, user.getUUID().toString());
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
return s.executeUpdate() > 0;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
|
||||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
|
||||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
|
|
||||||
postgresSQLUser.serverId = serverID;
|
|
||||||
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
|
|
||||||
s.setString(1, serverID);
|
|
||||||
s.setString(2, user.getUUID().toString());
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
return s.executeUpdate() > 0;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
postgresSQLHolder.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PostgresSQLUser constructUser(ResultSet set) throws SQLException {
|
|
||||||
return set.next() ? new PostgresSQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
|
||||||
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions()) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private User query(String sql, String value) throws IOException {
|
|
||||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
|
||||||
PreparedStatement s = c.prepareStatement(sql);
|
|
||||||
s.setString(1, value);
|
|
||||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
|
||||||
try (ResultSet set = s.executeQuery()) {
|
|
||||||
return constructUser(set);
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PostgresSQLUser implements User {
|
|
||||||
protected UUID uuid;
|
|
||||||
protected String username;
|
|
||||||
protected String accessToken;
|
|
||||||
protected String serverId;
|
|
||||||
protected String password;
|
|
||||||
protected ClientPermissions permissions;
|
|
||||||
|
|
||||||
public PostgresSQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
this.username = username;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.serverId = serverId;
|
|
||||||
this.password = password;
|
|
||||||
this.permissions = permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UUID getUUID() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServerId() {
|
|
||||||
return serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientPermissions getPermissions() {
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PostgresSQLUser{" +
|
|
||||||
"uuid=" + uuid +
|
|
||||||
", username='" + username + '\'' +
|
|
||||||
", permissions=" + permissions +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MySQLUserSession implements UserSession {
|
|
||||||
private final PostgresSQLUser user;
|
|
||||||
private final String id;
|
|
||||||
|
|
||||||
public MySQLUserSession(PostgresSQLUser user) {
|
|
||||||
this.user = user;
|
|
||||||
this.id = user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getID() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getExpireIn() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.auth.AuthException;
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
import pro.gravit.launchserver.manangers.AuthManager;
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -21,7 +21,7 @@ public User getUserByUUID(UUID uuid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,17 +41,17 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(LaunchServer server) {
|
public User checkServer(Client client, String username, String serverID) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,391 @@
|
||||||
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
|
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportExtendedCheckServer;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware, AuthSupportExtendedCheckServer {
|
||||||
|
public HikariSQLSourceConfig holder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
holder.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLSourceConfig getSQLConfig() {
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String hardwareIdColumn;
|
||||||
|
public String tableHWID = "hwids";
|
||||||
|
public String tableHWIDLog = "hwidLog";
|
||||||
|
public double criticalCompareLevel = 1.0;
|
||||||
|
private transient String sqlFindHardwareByPublicKey;
|
||||||
|
private transient String sqlFindHardwareByData;
|
||||||
|
private transient String sqlFindHardwareById;
|
||||||
|
private transient String sqlCreateHardware;
|
||||||
|
private transient String sqlCreateHWIDLog;
|
||||||
|
private transient String sqlUpdateHardwarePublicKey;
|
||||||
|
private transient String sqlUpdateHardwareBanned;
|
||||||
|
private transient String sqlUpdateUsers;
|
||||||
|
private transient String sqlUsersByHwidId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
|
holder.init();
|
||||||
|
super.init(server, pair);
|
||||||
|
String userInfoCols = makeUserCols();
|
||||||
|
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
|
||||||
|
if (sqlFindHardwareByPublicKey == null)
|
||||||
|
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE publicKey = ?".formatted(hardwareInfoCols, tableHWID);
|
||||||
|
if (sqlFindHardwareById == null)
|
||||||
|
sqlFindHardwareById = "SELECT %s FROM %s WHERE id = ?".formatted(hardwareInfoCols, tableHWID);
|
||||||
|
if (sqlUsersByHwidId == null)
|
||||||
|
sqlUsersByHwidId = "SELECT %s FROM %s WHERE %s = ?".formatted(userInfoCols, table, hardwareIdColumn);
|
||||||
|
if (sqlFindHardwareByData == null)
|
||||||
|
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
|
||||||
|
if (sqlCreateHardware == null)
|
||||||
|
sqlCreateHardware = "INSERT INTO %s (publickey, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, graphicCard, battery, banned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
|
||||||
|
if (sqlCreateHWIDLog == null)
|
||||||
|
sqlCreateHWIDLog = "INSERT INTO %s (hwidId, newPublicKey) VALUES (?, ?)".formatted(tableHWIDLog);
|
||||||
|
if (sqlUpdateHardwarePublicKey == null)
|
||||||
|
sqlUpdateHardwarePublicKey = "UPDATE %s SET publicKey = ? WHERE id = ?".formatted(tableHWID);
|
||||||
|
sqlUpdateHardwareBanned = "UPDATE %s SET banned = ? WHERE id = ?".formatted(tableHWID);
|
||||||
|
sqlUpdateUsers = "UPDATE %s SET %s = ? WHERE %s = ?".formatted(table, hardwareIdColumn, uuidColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String makeUserCols() {
|
||||||
|
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SQLUser constructUser(ResultSet set) throws SQLException {
|
||||||
|
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
||||||
|
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException {
|
||||||
|
HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo();
|
||||||
|
hardwareInfo.hwDiskId = set.getString("hwDiskId");
|
||||||
|
hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber");
|
||||||
|
byte[] displayId = set.getBytes("displayId");
|
||||||
|
hardwareInfo.displayId = displayId == null ? null : displayId;
|
||||||
|
hardwareInfo.bitness = set.getInt("bitness");
|
||||||
|
hardwareInfo.totalMemory = set.getLong("totalMemory");
|
||||||
|
hardwareInfo.logicalProcessors = set.getInt("logicalProcessors");
|
||||||
|
hardwareInfo.physicalProcessors = set.getInt("physicalProcessors");
|
||||||
|
hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq");
|
||||||
|
hardwareInfo.battery = set.getBoolean("battery");
|
||||||
|
hardwareInfo.graphicCard = set.getString("graphicCard");
|
||||||
|
byte[] publicKey = set.getBytes("publicKey");
|
||||||
|
long id = set.getLong("id");
|
||||||
|
boolean banned = set.getBoolean("banned");
|
||||||
|
return new SQLUserHardware(hardwareInfo, publicKey, id, banned);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) throws SQLException {
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlUpdateUsers);
|
||||||
|
s.setLong(1, hwidId);
|
||||||
|
s.setString(2, uuid.toString());
|
||||||
|
s.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey);
|
||||||
|
s.setBytes(1, publicKey);
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
if (set.next()) {
|
||||||
|
connection.commit();
|
||||||
|
return fetchHardwareInfo(set);
|
||||||
|
} else {
|
||||||
|
connection.commit();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
logger.error("SQL Error", throwables);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData);
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
SQLUserHardware hw = fetchHardwareInfo(set);
|
||||||
|
AuthSupportHardware.HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info);
|
||||||
|
if (result.compareLevel > criticalCompareLevel) {
|
||||||
|
connection.commit();
|
||||||
|
return hw;
|
||||||
|
} else {
|
||||||
|
connection.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
logger.error("SQL Error", throwables);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware getHardwareInfoById(String id) {
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlFindHardwareById);
|
||||||
|
s.setLong(1, Long.parseLong(id));
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
if (set.next()) {
|
||||||
|
connection.commit();
|
||||||
|
return fetchHardwareInfo(set);
|
||||||
|
} else {
|
||||||
|
connection.commit();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
logger.error("SQL Error", throwables);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) {
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
s.setBytes(1, publicKey);
|
||||||
|
s.setString(2, hardwareInfo.hwDiskId);
|
||||||
|
s.setString(3, hardwareInfo.baseboardSerialNumber);
|
||||||
|
s.setBytes(4, hardwareInfo.displayId == null ? null : hardwareInfo.displayId);
|
||||||
|
s.setInt(5, hardwareInfo.bitness);
|
||||||
|
s.setLong(6, hardwareInfo.totalMemory);
|
||||||
|
s.setInt(7, hardwareInfo.logicalProcessors);
|
||||||
|
s.setInt(8, hardwareInfo.physicalProcessors);
|
||||||
|
s.setLong(9, hardwareInfo.processorMaxFreq);
|
||||||
|
s.setString(10, hardwareInfo.graphicCard);
|
||||||
|
s.setBoolean(11, hardwareInfo.battery);
|
||||||
|
s.executeUpdate();
|
||||||
|
try (ResultSet generatedKeys = s.getGeneratedKeys()) {
|
||||||
|
if (generatedKeys.next()) {
|
||||||
|
//writeHwidLog(connection, generatedKeys.getLong(1), publicKey);
|
||||||
|
long id = generatedKeys.getLong(1);
|
||||||
|
connection.commit();
|
||||||
|
return new SQLUserHardware(hardwareInfo, publicKey, id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connection.commit();
|
||||||
|
return null;
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
logger.error("SQL Error", throwables);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
|
||||||
|
AbstractSQLCoreProvider.SQLUserSession SQLUserSession = (AbstractSQLCoreProvider.SQLUserSession) userSession;
|
||||||
|
SQLUser SQLUser = (SQLUser) SQLUserSession.getUser();
|
||||||
|
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
|
||||||
|
if (SQLUser.hwidId == SQLUserHardware.id) return;
|
||||||
|
SQLUser.hwidId = SQLUserHardware.id;
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
setUserHardwareId(connection, SQLUser.getUUID(), SQLUserHardware.id);
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
logger.error("SQL Error", throwables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
|
||||||
|
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
|
||||||
|
SQLUserHardware.publicKey = publicKey;
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey);
|
||||||
|
s.setBytes(1, publicKey);
|
||||||
|
s.setLong(2, SQLUserHardware.id);
|
||||||
|
s.executeUpdate();
|
||||||
|
connection.commit();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
|
||||||
|
List<User> users = new LinkedList<>();
|
||||||
|
try (Connection c = holder.getConnection()) {
|
||||||
|
c.setAutoCommit(false);
|
||||||
|
PreparedStatement s = c.prepareStatement(sqlUsersByHwidId);
|
||||||
|
s.setLong(1, Long.parseLong(hardware.getId()));
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
while (!set.isLast()) {
|
||||||
|
users.add(constructUser(set));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.commit();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void banHardware(UserHardware hardware) {
|
||||||
|
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
|
||||||
|
SQLUserHardware.banned = true;
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
|
||||||
|
s.setBoolean(1, true);
|
||||||
|
s.setLong(2, SQLUserHardware.id);
|
||||||
|
s.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("SQL Error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbanHardware(UserHardware hardware) {
|
||||||
|
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
|
||||||
|
SQLUserHardware.banned = false;
|
||||||
|
try (Connection connection = holder.getConnection()) {
|
||||||
|
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
|
||||||
|
s.setBoolean(1, false);
|
||||||
|
s.setLong(2, SQLUserHardware.id);
|
||||||
|
s.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("SQL error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractSQLCoreProvider.SQLUserSession createSession(AbstractSQLCoreProvider.SQLUser user) {
|
||||||
|
return new SQLUserSession(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSession extendedCheckServer(Client client, String username, String serverID) {
|
||||||
|
AbstractSQLCoreProvider.SQLUser user = (AbstractSQLCoreProvider.SQLUser) getUserByUsername(username);
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
|
||||||
|
return createSession(user);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SQLUserSession extends AbstractSQLCoreProvider.SQLUserSession implements UserSessionSupportHardware {
|
||||||
|
private transient SQLUser SQLUser;
|
||||||
|
protected transient SQLUserHardware hardware;
|
||||||
|
|
||||||
|
public SQLUserSession(AbstractSQLCoreProvider.SQLUser user) {
|
||||||
|
super(user);
|
||||||
|
SQLUser = (SQLUser) user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHardwareId() {
|
||||||
|
return SQLUser.hwidId == 0 ? null : String.valueOf(SQLUser.hwidId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserHardware getHardware() {
|
||||||
|
if(hardware == null) {
|
||||||
|
hardware = (SQLUserHardware) getHardwareInfoById(String.valueOf(SQLUser.hwidId));
|
||||||
|
}
|
||||||
|
return hardware;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SQLUserHardware implements UserHardware {
|
||||||
|
private final HardwareReportRequest.HardwareInfo hardwareInfo;
|
||||||
|
private final long id;
|
||||||
|
private byte[] publicKey;
|
||||||
|
private boolean banned;
|
||||||
|
|
||||||
|
public SQLUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
|
||||||
|
this.hardwareInfo = hardwareInfo;
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.id = id;
|
||||||
|
this.banned = banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
|
||||||
|
return hardwareInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return String.valueOf(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBanned() {
|
||||||
|
return banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SQLUserHardware{" +
|
||||||
|
"hardwareInfo=" + hardwareInfo +
|
||||||
|
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
|
||||||
|
", id=" + id +
|
||||||
|
", banned=" + banned +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SQLUser extends AbstractSQLCoreProvider.SQLUser {
|
||||||
|
protected long hwidId;
|
||||||
|
|
||||||
|
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
|
||||||
|
super(uuid, username, accessToken, serverId, password);
|
||||||
|
this.hwidId = hwidId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SQLUser{" +
|
||||||
|
"uuid=" + uuid +
|
||||||
|
", username='" + username + '\'' +
|
||||||
|
", permissions=" + permissions +
|
||||||
|
", hwidId=" + hwidId +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
package pro.gravit.launchserver.auth.core;
|
||||||
|
|
||||||
import pro.gravit.launcher.ClientPermissions;
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -9,10 +9,6 @@ public interface User {
|
||||||
|
|
||||||
UUID getUUID();
|
UUID getUUID();
|
||||||
|
|
||||||
String getServerId();
|
|
||||||
|
|
||||||
String getAccessToken();
|
|
||||||
|
|
||||||
ClientPermissions getPermissions();
|
ClientPermissions getPermissions();
|
||||||
|
|
||||||
default boolean isBanned() {
|
default boolean isBanned() {
|
||||||
|
|
|
@ -5,5 +5,7 @@ public interface UserSession {
|
||||||
|
|
||||||
User getUser();
|
User getUser();
|
||||||
|
|
||||||
|
String getMinecraftAccessToken();
|
||||||
|
|
||||||
long getExpireIn();
|
long getExpireIn();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces;
|
package pro.gravit.launchserver.auth.core.interfaces;
|
||||||
|
|
||||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||||
|
|
||||||
public interface UserHardware {
|
public interface UserHardware {
|
||||||
HardwareReportRequest.HardwareInfo getHardwareInfo();
|
HardwareReportRequest.HardwareInfo getHardwareInfo();
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
|
||||||
|
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
|
||||||
|
import pro.gravit.launcher.base.events.request.GetAssetUploadUrlRequestEvent;
|
||||||
|
import pro.gravit.launchserver.auth.Feature;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Feature(GetAssetUploadUrlRequestEvent.FEATURE_NAME)
|
||||||
|
public interface AuthSupportAssetUpload extends AuthSupport {
|
||||||
|
String getAssetUploadUrl(String name, User user);
|
||||||
|
|
||||||
|
default AuthRequestEvent.OAuthRequestEvent getAssetUploadToken(String name, User user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
|
||||||
|
return new AssetUploadInfoRequestEvent(Set.of("SKIN", "CAPE"), AssetUploadInfoRequestEvent.SlimSupportConf.USER);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
import pro.gravit.launchserver.auth.core.UserSession;
|
import pro.gravit.launchserver.auth.core.UserSession;
|
||||||
|
|
||||||
public interface AuthSupportExit extends AuthSupport {
|
public interface AuthSupportExit extends AuthSupport {
|
||||||
boolean deleteSession(UserSession session);
|
void deleteSession(UserSession session);
|
||||||
|
|
||||||
boolean exitUser(User user);
|
void exitUser(User user);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.core.UserSession;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface AuthSupportExtendedCheckServer {
|
||||||
|
UserSession extendedCheckServer(Client client, String username, String serverID);
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.auth.Feature;
|
|
||||||
import pro.gravit.launchserver.auth.core.User;
|
|
||||||
import pro.gravit.launchserver.auth.core.UserSession;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Feature("sessions")
|
|
||||||
public interface AuthSupportGetSessionsFromUser extends AuthSupport {
|
|
||||||
List<UserSession> getSessionsByUser(User user);
|
|
||||||
}
|
|
|
@ -1,10 +1,9 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||||
|
|
||||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
|
||||||
import pro.gravit.launchserver.auth.core.User;
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
import pro.gravit.launchserver.auth.core.UserSession;
|
import pro.gravit.launchserver.auth.core.UserSession;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
|
|
||||||
import pro.gravit.launchserver.helper.DamerauHelper;
|
import pro.gravit.launchserver.helper.DamerauHelper;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -28,10 +27,6 @@ public interface AuthSupportHardware extends AuthSupport {
|
||||||
|
|
||||||
void unbanHardware(UserHardware hardware);
|
void unbanHardware(UserHardware hardware);
|
||||||
|
|
||||||
default UserSupportHardware fetchUserHardware(User user) {
|
|
||||||
return (UserSupportHardware) user;
|
|
||||||
}
|
|
||||||
|
|
||||||
default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) {
|
default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) {
|
||||||
if (hardwareInfo.baseboardSerialNumber != null)
|
if (hardwareInfo.baseboardSerialNumber != null)
|
||||||
hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim();
|
hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||||
|
|
||||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
import pro.gravit.launchserver.auth.Feature;
|
import pro.gravit.launchserver.auth.Feature;
|
||||||
import pro.gravit.launchserver.auth.core.User;
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface AuthSupportSudo {
|
||||||
|
AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException;
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.auth.core.User;
|
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportBanInfo;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
public interface AuthSupportUserBan extends AuthSupport {
|
|
||||||
UserSupportBanInfo.UserBanInfo banUser(User user, String reason, String moderator, LocalDateTime startTime, LocalDateTime endTime);
|
|
||||||
|
|
||||||
default UserSupportBanInfo.UserBanInfo banUser(User user) {
|
|
||||||
return banUser(user, null, null, LocalDateTime.now(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unbanUser(User user);
|
|
||||||
|
|
||||||
default UserSupportBanInfo fetchUserBanInfo(User user) {
|
|
||||||
return (UserSupportBanInfo) user;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.session;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
|
|
||||||
|
public interface UserSessionSupportHardware {
|
||||||
|
String getHardwareId();
|
||||||
|
UserHardware getHardware();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.session;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
public interface UserSessionSupportKeys {
|
||||||
|
ClientProfileKeys getClientProfileKeys();
|
||||||
|
|
||||||
|
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt,
|
||||||
|
long refreshedAfter) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.interfaces.session;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface UserSessionSupportProperties {
|
||||||
|
Map<String, String> getProperties();
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.user;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
public interface UserSupportBanInfo {
|
|
||||||
UserBanInfo getBanInfo();
|
|
||||||
|
|
||||||
interface UserBanInfo {
|
|
||||||
String getId();
|
|
||||||
|
|
||||||
default String getReason() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default String getModerator() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default LocalDateTime getStartDate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default LocalDateTime getEndDate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.user;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
|
||||||
|
|
||||||
public interface UserSupportHardware {
|
|
||||||
UserHardware getHardware();
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.user;
|
package pro.gravit.launchserver.auth.core.interfaces.user;
|
||||||
|
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -23,10 +23,10 @@ default Map<String, Texture> getUserAssets() {
|
||||||
var skin = getSkinTexture();
|
var skin = getSkinTexture();
|
||||||
var cape = getCloakTexture();
|
var cape = getCloakTexture();
|
||||||
Map<String, Texture> map = new HashMap<>();
|
Map<String, Texture> map = new HashMap<>();
|
||||||
if(skin != null) {
|
if (skin != null) {
|
||||||
map.put("SKIN", skin);
|
map.put("SKIN", skin);
|
||||||
}
|
}
|
||||||
if(cape != null) {
|
if (cape != null) {
|
||||||
map.put("CAPE", cape);
|
map.put("CAPE", cape);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public record AccessTokenResponse(@SerializedName("access_token") String accessToken,
|
||||||
|
@SerializedName("expires_in") Long expiresIn,
|
||||||
|
@SerializedName("refresh_expires_in") Long refreshExpiresIn,
|
||||||
|
@SerializedName("refresh_token") String refreshToken,
|
||||||
|
@SerializedName("token_type") String tokenType,
|
||||||
|
@SerializedName("id_token") String idToken,
|
||||||
|
@SerializedName("not-before-policy") Integer notBeforePolicy,
|
||||||
|
@SerializedName("session_state") String sessionState,
|
||||||
|
@SerializedName("scope") String scope) {
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
|
||||||
|
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
|
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
import pro.gravit.launchserver.auth.core.UserSession;
|
||||||
|
import pro.gravit.launchserver.manangers.AuthManager;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class OpenIDAuthCoreProvider extends AuthCoreProvider {
|
||||||
|
private transient SQLUserStore sqlUserStore;
|
||||||
|
private transient SQLServerSessionStore sqlSessionStore;
|
||||||
|
private transient OpenIDAuthenticator openIDAuthenticator;
|
||||||
|
|
||||||
|
private OpenIDConfig openIDConfig;
|
||||||
|
private HikariSQLSourceConfig sqlSourceConfig;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
|
||||||
|
return openIDAuthenticator.getDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUsername(String username) {
|
||||||
|
return sqlUserStore.getByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUUID(UUID uuid) {
|
||||||
|
return sqlUserStore.getUserByUUID(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||||
|
return openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport refreshAccessToken(String oldRefreshToken, AuthResponse.AuthContext context) {
|
||||||
|
var tokens = openIDAuthenticator.refreshAccessToken(oldRefreshToken);
|
||||||
|
var accessToken = tokens.accessToken();
|
||||||
|
var refreshToken = tokens.refreshToken();
|
||||||
|
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
|
||||||
|
|
||||||
|
UserSession session;
|
||||||
|
try {
|
||||||
|
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
|
||||||
|
} catch (OAuthAccessTokenExpired e) {
|
||||||
|
throw new RuntimeException("invalid token", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
|
||||||
|
expiresIn, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||||
|
if (password == null) {
|
||||||
|
throw AuthException.wrongPassword();
|
||||||
|
}
|
||||||
|
var authCodePassword = (AuthCodePassword) password;
|
||||||
|
|
||||||
|
var tokens = openIDAuthenticator.authorize(authCodePassword);
|
||||||
|
|
||||||
|
var accessToken = tokens.accessToken();
|
||||||
|
var refreshToken = tokens.refreshToken();
|
||||||
|
var user = openIDAuthenticator.createUserFromToken(accessToken);
|
||||||
|
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
|
||||||
|
|
||||||
|
sqlUserStore.createOrUpdateUser(user);
|
||||||
|
|
||||||
|
UserSession session;
|
||||||
|
try {
|
||||||
|
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
|
||||||
|
} catch (OAuthAccessTokenExpired e) {
|
||||||
|
throw new AuthException("invalid token", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minecraftAccess) {
|
||||||
|
var minecraftToken = generateMinecraftToken(user);
|
||||||
|
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftToken, accessToken, refreshToken,
|
||||||
|
expiresIn, session);
|
||||||
|
} else {
|
||||||
|
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
|
||||||
|
expiresIn, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateMinecraftToken(User user) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.issuer("LaunchServer")
|
||||||
|
.subject(user.getUUID().toString())
|
||||||
|
.claim("preferred_username", user.getUsername())
|
||||||
|
.expiration(Date.from(Instant.now().plus(24, ChronoUnit.HOURS)))
|
||||||
|
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
private User createUserFromMinecraftToken(String accessToken) throws AuthException {
|
||||||
|
try {
|
||||||
|
var parser = Jwts.parser()
|
||||||
|
.requireIssuer("LaunchServer")
|
||||||
|
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
|
||||||
|
.build();
|
||||||
|
var claims = parser.parseSignedClaims(accessToken);
|
||||||
|
var username = claims.getPayload().get("preferred_username", String.class);
|
||||||
|
var uuid = UUID.fromString(claims.getPayload().getSubject());
|
||||||
|
return new UserEntity(username, uuid, new ClientPermissions());
|
||||||
|
} catch (JwtException e) {
|
||||||
|
throw new AuthException("Bad minecraft token", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server, AuthProviderPair pair) {
|
||||||
|
super.init(server, pair);
|
||||||
|
this.sqlSourceConfig.init();
|
||||||
|
this.sqlUserStore = new SQLUserStore(sqlSourceConfig);
|
||||||
|
this.sqlUserStore.init();
|
||||||
|
this.sqlSessionStore = new SQLServerSessionStore(sqlSourceConfig);
|
||||||
|
this.sqlSessionStore.init();
|
||||||
|
this.openIDAuthenticator = new OpenIDAuthenticator(openIDConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User checkServer(Client client, String username, String serverID) {
|
||||||
|
var savedServerId = sqlSessionStore.getServerIdByUsername(username);
|
||||||
|
if (!serverID.equals(savedServerId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlUserStore.getByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
|
||||||
|
User user;
|
||||||
|
try {
|
||||||
|
user = createUserFromMinecraftToken(accessToken);
|
||||||
|
} catch (AuthException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user.getUUID().equals(uuid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlUserStore.createOrUpdateUser(user);
|
||||||
|
|
||||||
|
return sqlSessionStore.joinServer(user.getUUID(), user.getUsername(), serverID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
sqlSourceConfig.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.security.Jwk;
|
||||||
|
import io.jsonwebtoken.security.JwkSet;
|
||||||
|
import io.jsonwebtoken.security.Jwks;
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
|
||||||
|
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
|
||||||
|
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
import pro.gravit.launchserver.auth.core.UserSession;
|
||||||
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
|
import pro.gravit.utils.helper.QueryHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class OpenIDAuthenticator {
|
||||||
|
private static final HttpClient CLIENT = HttpClient.newBuilder().build();
|
||||||
|
private final OpenIDConfig openIDConfig;
|
||||||
|
private final JwtParser jwtParser;
|
||||||
|
|
||||||
|
public OpenIDAuthenticator(OpenIDConfig openIDConfig) {
|
||||||
|
this.openIDConfig = openIDConfig;
|
||||||
|
var keyLocator = loadKeyLocator(openIDConfig);
|
||||||
|
this.jwtParser = Jwts.parser()
|
||||||
|
.keyLocator(keyLocator)
|
||||||
|
.requireIssuer(openIDConfig.issuer())
|
||||||
|
.require("azp", openIDConfig.clientId())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails() {
|
||||||
|
var state = UUID.randomUUID().toString();
|
||||||
|
var uri = QueryBuilder.get(openIDConfig.authorizationEndpoint())
|
||||||
|
.addQuery("response_type", "code")
|
||||||
|
.addQuery("client_id", openIDConfig.clientId())
|
||||||
|
.addQuery("redirect_uri", openIDConfig.redirectUri())
|
||||||
|
.addQuery("scope", openIDConfig.scopes())
|
||||||
|
.addQuery("state", state)
|
||||||
|
.toUriString();
|
||||||
|
|
||||||
|
return List.of(new AuthWebViewDetails(uri, openIDConfig.redirectUri()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenResponse refreshAccessToken(String oldRefreshToken) {
|
||||||
|
var postBody = QueryBuilder.post()
|
||||||
|
.addQuery("grant_type", "refresh_token")
|
||||||
|
.addQuery("refresh_token", oldRefreshToken)
|
||||||
|
.addQuery("client_id", openIDConfig.clientId())
|
||||||
|
.addQuery("client_secret", openIDConfig.clientSecret())
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
var accessTokenResponse = requestToken(postBody);
|
||||||
|
var accessToken = accessTokenResponse.accessToken();
|
||||||
|
var refreshToken = accessTokenResponse.refreshToken();
|
||||||
|
|
||||||
|
try {
|
||||||
|
readAndVerifyToken(accessToken);
|
||||||
|
} catch (AuthException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
|
||||||
|
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
|
||||||
|
|
||||||
|
return new TokenResponse(accessToken, accessTokenExpiresIn,
|
||||||
|
refreshToken, refreshTokenExpiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws AuthCoreProvider.OAuthAccessTokenExpired {
|
||||||
|
Jws<Claims> token;
|
||||||
|
try {
|
||||||
|
token = readAndVerifyToken(accessToken);
|
||||||
|
} catch (AuthException e) {
|
||||||
|
throw new AuthCoreProvider.OAuthAccessTokenExpired("Can't read token", e);
|
||||||
|
}
|
||||||
|
var user = createUserFromToken(token);
|
||||||
|
long expiresIn = 0;
|
||||||
|
var expDate = token.getPayload().getExpiration();
|
||||||
|
if (expDate != null) {
|
||||||
|
expiresIn = expDate.toInstant().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OpenIDUserSession(user, accessToken, expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenResponse authorize(AuthCodePassword authCode) throws IOException {
|
||||||
|
var uri = URI.create(authCode.uri);
|
||||||
|
var queries = QueryHelper.splitUriQuery(uri);
|
||||||
|
|
||||||
|
String code = CommonHelper.multimapFirstOrNullValue("code", queries);
|
||||||
|
String error = CommonHelper.multimapFirstOrNullValue("error", queries);
|
||||||
|
String errorDescription = CommonHelper.multimapFirstOrNullValue("error_description", queries);
|
||||||
|
|
||||||
|
if (error != null && !error.isBlank()) {
|
||||||
|
throw new AuthException("Auth error. Error: %s, description: %s".formatted(error, errorDescription));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var postBody = QueryBuilder.post()
|
||||||
|
.addQuery("grant_type", "authorization_code")
|
||||||
|
.addQuery("code", code)
|
||||||
|
.addQuery("redirect_uri", openIDConfig.redirectUri())
|
||||||
|
.addQuery("client_id", openIDConfig.clientId())
|
||||||
|
.addQuery("client_secret", openIDConfig.clientSecret())
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
var accessTokenResponse = requestToken(postBody);
|
||||||
|
var accessToken = accessTokenResponse.accessToken();
|
||||||
|
var refreshToken = accessTokenResponse.refreshToken();
|
||||||
|
|
||||||
|
readAndVerifyToken(accessToken);
|
||||||
|
|
||||||
|
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
|
||||||
|
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
|
||||||
|
|
||||||
|
return new TokenResponse(accessToken, accessTokenExpiresIn,
|
||||||
|
refreshToken, refreshTokenExpiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User createUserFromToken(String accessToken) throws AuthException {
|
||||||
|
return createUserFromToken(readAndVerifyToken(accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Jws<Claims> readAndVerifyToken(String accessToken) throws AuthException {
|
||||||
|
if (accessToken == null) {
|
||||||
|
throw new AuthException("Token is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return jwtParser.parseSignedClaims(accessToken);
|
||||||
|
} catch (JwtException e) {
|
||||||
|
throw new AuthException("Bad token", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private User createUserFromToken(Jws<Claims> token) {
|
||||||
|
var username = token.getPayload().get(openIDConfig.extractorConfig().usernameClaim(), String.class);
|
||||||
|
var uuidStr = token.getPayload().get(openIDConfig.extractorConfig().uuidClaim(), String.class);
|
||||||
|
var uuid = UUID.fromString(uuidStr);
|
||||||
|
return new UserEntity(username, uuid, new ClientPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccessTokenResponse requestToken(String postBody) {
|
||||||
|
var request = HttpRequest.newBuilder()
|
||||||
|
.uri(openIDConfig.tokenUri())
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(postBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> resp;
|
||||||
|
try {
|
||||||
|
resp = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return Launcher.gsonManager.gson.fromJson(resp.body(), AccessTokenResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyLocator loadKeyLocator(OpenIDConfig openIDConfig) {
|
||||||
|
var request = HttpRequest.newBuilder(openIDConfig.jwksUri()).GET().build();
|
||||||
|
HttpResponse<String> response;
|
||||||
|
try {
|
||||||
|
response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
var jwks = Jwks.setParser().build().parse(response.body());
|
||||||
|
return new KeyLocator(jwks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KeyLocator extends LocatorAdapter<Key> {
|
||||||
|
private final Map<String, Key> keys;
|
||||||
|
|
||||||
|
public KeyLocator(JwkSet jwks) {
|
||||||
|
this.keys = jwks.getKeys().stream().collect(
|
||||||
|
Collectors.toMap(jwk -> String.valueOf(jwk.get("kid")), Jwk::toKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Key locate(JweHeader header) {
|
||||||
|
return super.locate(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Key locate(JwsHeader header) {
|
||||||
|
return keys.get(header.getKeyId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Key doLocate(Header header) {
|
||||||
|
return super.doLocate(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record OpenIDUserSession(User user, String token, long expiresIn) implements UserSession {
|
||||||
|
@Override
|
||||||
|
public String getID() {
|
||||||
|
return user.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMinecraftAccessToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getExpireIn() {
|
||||||
|
return expiresIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public record OpenIDConfig(URI tokenUri, String authorizationEndpoint, String clientId, String clientSecret,
|
||||||
|
String redirectUri, URI jwksUri, String scopes, String issuer,
|
||||||
|
ClaimExtractorConfig extractorConfig) {
|
||||||
|
|
||||||
|
public record ClaimExtractorConfig(String usernameClaim, String uuidClaim) {}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Xakep_SDK
|
||||||
|
*/
|
||||||
|
public class QueryBuilder {
|
||||||
|
private final String uri;
|
||||||
|
private final StringBuilder query = new StringBuilder();
|
||||||
|
|
||||||
|
public QueryBuilder(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryBuilder get(String uri) {
|
||||||
|
Objects.requireNonNull(uri, "uri");
|
||||||
|
if (uri.endsWith("/")) {
|
||||||
|
uri = uri.substring(0, uri.length() - 1);
|
||||||
|
}
|
||||||
|
return new QueryBuilder(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryBuilder post() {
|
||||||
|
return new QueryBuilder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryBuilder addQuery(String key, String value) {
|
||||||
|
if (!query.isEmpty()) {
|
||||||
|
query.append('&');
|
||||||
|
}
|
||||||
|
query.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
|
||||||
|
.append('=')
|
||||||
|
.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toUriString() {
|
||||||
|
if (uri != null) {
|
||||||
|
if (query. isEmpty()) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
return uri + '?' + query;
|
||||||
|
}
|
||||||
|
return toQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toQueryString() {
|
||||||
|
return query.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toUriString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SQLServerSessionStore implements ServerSessionStore {
|
||||||
|
private static final String CREATE_TABLE = """
|
||||||
|
create table if not exists `gravit_server_session` (
|
||||||
|
id int auto_increment,
|
||||||
|
uuid varchar(36),
|
||||||
|
username varchar(255),
|
||||||
|
server_id varchar(41),
|
||||||
|
primary key (id),
|
||||||
|
unique (uuid),
|
||||||
|
unique (username)
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
private static final String DELETE_SERVER_ID = """
|
||||||
|
delete from `gravit_server_session` where uuid = ?
|
||||||
|
""";
|
||||||
|
private static final String INSERT_SERVER_ID = """
|
||||||
|
insert into `gravit_server_session` (uuid, username, server_id) values (?, ?, ?)
|
||||||
|
""";
|
||||||
|
private static final String SELECT_SERVER_ID_BY_USERNAME = """
|
||||||
|
select server_id from `gravit_server_session` where username = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
private final SQLSourceConfig sqlSourceConfig;
|
||||||
|
|
||||||
|
public SQLServerSessionStore(SQLSourceConfig sqlSourceConfig) {
|
||||||
|
this.sqlSourceConfig = sqlSourceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean joinServer(UUID uuid, String username, String serverId) {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
var savepoint = connection.setSavepoint();
|
||||||
|
try (var deleteServerIdStmt = connection.prepareStatement(DELETE_SERVER_ID);
|
||||||
|
var insertServerIdStmt = connection.prepareStatement(INSERT_SERVER_ID)) {
|
||||||
|
deleteServerIdStmt.setString(1, uuid.toString());
|
||||||
|
deleteServerIdStmt.execute();
|
||||||
|
insertServerIdStmt.setString(1, uuid.toString());
|
||||||
|
insertServerIdStmt.setString(2, username);
|
||||||
|
insertServerIdStmt.setString(3, serverId);
|
||||||
|
insertServerIdStmt.execute();
|
||||||
|
connection.commit();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
connection.rollback(savepoint);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LogHelper.debug("Can't join server. Username: %s".formatted(username));
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServerIdByUsername(String username) {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection();
|
||||||
|
var selectServerId = connection.prepareStatement(SELECT_SERVER_ID_BY_USERNAME)) {
|
||||||
|
selectServerId.setString(1, username);
|
||||||
|
try (var rs = selectServerId.executeQuery()) {
|
||||||
|
if (!rs.next()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rs.getString("server_id");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LogHelper.debug("Can't find server id by username. Username: %s".formatted(username));
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
var savepoint = connection.setSavepoint();
|
||||||
|
try (var createTableStmt = connection.prepareStatement(CREATE_TABLE)) {
|
||||||
|
createTableStmt.execute();
|
||||||
|
connection.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
connection.rollback(savepoint);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SQLUserStore implements UserStore {
|
||||||
|
private static final String CREATE_USER_TABLE = """
|
||||||
|
create table if not exists `gravit_user` (
|
||||||
|
id int auto_increment,
|
||||||
|
uuid varchar(36),
|
||||||
|
username varchar(255),
|
||||||
|
primary key (id),
|
||||||
|
unique (uuid),
|
||||||
|
unique (username)
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
private static final String INSERT_USER = """
|
||||||
|
insert into `gravit_user` (uuid, username) values (?, ?)
|
||||||
|
""";
|
||||||
|
private static final String DELETE_USER_BY_NAME = """
|
||||||
|
delete `gravit_user` where username = ?
|
||||||
|
""";
|
||||||
|
private static final String SELECT_USER_BY_NAME = """
|
||||||
|
select uuid, username from `gravit_user` where username = ?
|
||||||
|
""";
|
||||||
|
private static final String SELECT_USER_BY_UUID = """
|
||||||
|
select uuid, username from `gravit_user` where uuid = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
private final HikariSQLSourceConfig sqlSourceConfig;
|
||||||
|
|
||||||
|
public SQLUserStore(HikariSQLSourceConfig sqlSourceConfig) {
|
||||||
|
this.sqlSourceConfig = sqlSourceConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getByUsername(String username) {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection();
|
||||||
|
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_NAME)) {
|
||||||
|
selectUserStmt.setString(1, username);
|
||||||
|
try (var rs = selectUserStmt.executeQuery()) {
|
||||||
|
if (!rs.next()) {
|
||||||
|
LogHelper.debug("User not found, username: %s".formatted(username));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new UserEntity(rs.getString("username"),
|
||||||
|
UUID.fromString(rs.getString("uuid")),
|
||||||
|
new ClientPermissions());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUUID(UUID uuid) {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection();
|
||||||
|
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_UUID)) {
|
||||||
|
selectUserStmt.setString(1, uuid.toString());
|
||||||
|
try (var rs = selectUserStmt.executeQuery()) {
|
||||||
|
if (!rs.next()) {
|
||||||
|
LogHelper.debug("User not found, UUID: %s".formatted(uuid));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new UserEntity(rs.getString("username"),
|
||||||
|
UUID.fromString(rs.getString("uuid")),
|
||||||
|
new ClientPermissions());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createOrUpdateUser(User user) {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
var savepoint = connection.setSavepoint();
|
||||||
|
try (var deleteUserStmt = connection.prepareStatement(DELETE_USER_BY_NAME);
|
||||||
|
var insertUserStmt = connection.prepareStatement(INSERT_USER)) {
|
||||||
|
deleteUserStmt.setString(1, user.getUsername());
|
||||||
|
deleteUserStmt.execute();
|
||||||
|
insertUserStmt.setString(1, user.getUUID().toString());
|
||||||
|
insertUserStmt.setString(2, user.getUsername());
|
||||||
|
insertUserStmt.execute();
|
||||||
|
connection.commit();
|
||||||
|
LogHelper.debug("User saved. UUID: %s, username: %s".formatted(user.getUUID(), user.getUsername()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
connection.rollback(savepoint);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LogHelper.debug("Failed to save user");
|
||||||
|
LogHelper.error(e);
|
||||||
|
throw new RuntimeException("Failed to save user", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
try (var connection = sqlSourceConfig.getConnection()) {
|
||||||
|
connection.setAutoCommit(false);
|
||||||
|
var savepoint = connection.setSavepoint();
|
||||||
|
try (var createUserTableStmt = connection.prepareStatement(CREATE_USER_TABLE)) {
|
||||||
|
createUserTableStmt.execute();
|
||||||
|
connection.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
connection.rollback(savepoint);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface ServerSessionStore {
|
||||||
|
boolean joinServer(UUID uuid, String username, String serverId);
|
||||||
|
String getServerIdByUsername(String username);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
public record TokenResponse(String accessToken, long accessTokenExpiresIn,
|
||||||
|
String refreshToken, long refreshTokenExpiresIn) {
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.ClientPermissions;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
record UserEntity(String username, UUID uuid, ClientPermissions permissions) implements User {
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientPermissions getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package pro.gravit.launchserver.auth.core.openid;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface UserStore {
|
||||||
|
User getByUsername(String username);
|
||||||
|
|
||||||
|
User getUserByUUID(UUID uuid);
|
||||||
|
|
||||||
|
void createOrUpdateUser(User user);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package pro.gravit.launchserver.auth.mix;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
|
public abstract class MixProvider implements AutoCloseable{
|
||||||
|
public static final ProviderMap<MixProvider> providers = new ProviderMap<>("MixProvider");
|
||||||
|
private static final Logger logger = LogManager.getLogger();
|
||||||
|
private static boolean registredProviders = false;
|
||||||
|
|
||||||
|
public static void registerProviders() {
|
||||||
|
if (!registredProviders) {
|
||||||
|
providers.register("uploadAsset", UploadAssetMixProvider.class);
|
||||||
|
registredProviders = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void init(LaunchServer server, AuthCoreProvider core);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T isSupport(Class<T> clazz) {
|
||||||
|
if (clazz.isAssignableFrom(getClass())) return (T) this;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void close();
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package pro.gravit.launchserver.auth.mix;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
||||||
|
import pro.gravit.launchserver.auth.core.User;
|
||||||
|
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportAssetUpload;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class UploadAssetMixProvider extends MixProvider implements AuthSupportAssetUpload {
|
||||||
|
public Map<String, String> urls;
|
||||||
|
public AssetUploadInfoRequestEvent.SlimSupportConf slimSupportConf;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAssetUploadUrl(String name, User user) {
|
||||||
|
return urls.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
|
||||||
|
return new AssetUploadInfoRequestEvent(urls.keySet(), slimSupportConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server, AuthCoreProvider core) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package pro.gravit.launchserver.auth.password;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
public class BCryptPasswordVerifier extends PasswordVerifier {
|
||||||
|
public int cost = 10;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(String encryptedPassword, String password) {
|
||||||
|
return OpenBSDBCrypt.checkPassword(encryptedPassword, password.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encrypt(String password) {
|
||||||
|
return OpenBSDBCrypt.generate(password.toCharArray(), SecurityHelper.randomBytes(16), cost);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -14,34 +14,11 @@
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class JsonPasswordVerifier extends PasswordVerifier {
|
public class JsonPasswordVerifier extends PasswordVerifier {
|
||||||
private static transient final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private transient final HttpClient client = HttpClient.newBuilder().build();
|
private transient final HttpClient client = HttpClient.newBuilder().build();
|
||||||
public String url;
|
public String url;
|
||||||
public String bearerToken;
|
public String bearerToken;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean check(String encryptedPassword, String password) {
|
|
||||||
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
|
|
||||||
if (response != null) {
|
|
||||||
return response.success;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JsonPasswordRequest {
|
|
||||||
public String encryptedPassword;
|
|
||||||
public String password;
|
|
||||||
|
|
||||||
public JsonPasswordRequest(String encryptedPassword, String password) {
|
|
||||||
this.encryptedPassword = encryptedPassword;
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JsonPasswordResponse {
|
|
||||||
public boolean success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
|
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
|
||||||
HttpRequest.BodyPublisher publisher;
|
HttpRequest.BodyPublisher publisher;
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
|
@ -78,4 +55,27 @@ public static <T, R> R jsonRequest(T request, String url, String bearerToken, Cl
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(String encryptedPassword, String password) {
|
||||||
|
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
|
||||||
|
if (response != null) {
|
||||||
|
return response.success;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JsonPasswordRequest {
|
||||||
|
public String encryptedPassword;
|
||||||
|
public String password;
|
||||||
|
|
||||||
|
public JsonPasswordRequest(String encryptedPassword, String password) {
|
||||||
|
this.encryptedPassword = encryptedPassword;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JsonPasswordResponse {
|
||||||
|
public boolean success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ public static void registerProviders() {
|
||||||
providers.register("digest", DigestPasswordVerifier.class);
|
providers.register("digest", DigestPasswordVerifier.class);
|
||||||
providers.register("doubleDigest", DoubleDigestPasswordVerifier.class);
|
providers.register("doubleDigest", DoubleDigestPasswordVerifier.class);
|
||||||
providers.register("json", JsonPasswordVerifier.class);
|
providers.register("json", JsonPasswordVerifier.class);
|
||||||
|
providers.register("bcrypt", BCryptPasswordVerifier.class);
|
||||||
providers.register("accept", AcceptPasswordVerifier.class);
|
providers.register("accept", AcceptPasswordVerifier.class);
|
||||||
providers.register("reject", RejectPasswordVerifier.class);
|
providers.register("reject", RejectPasswordVerifier.class);
|
||||||
registeredProviders = true;
|
registeredProviders = true;
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
package pro.gravit.launchserver.auth.profiles;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class LocalProfileProvider extends ProfileProvider {
|
||||||
|
public String profilesDir = "profiles";
|
||||||
|
private transient volatile Map<Path, ClientProfile> profilesMap;
|
||||||
|
private transient volatile Set<ClientProfile> profilesList; // Cache
|
||||||
|
@Override
|
||||||
|
public void sync() throws IOException {
|
||||||
|
Path profilesDirPath = Path.of(profilesDir);
|
||||||
|
if (!IOHelper.isDir(profilesDirPath))
|
||||||
|
Files.createDirectory(profilesDirPath);
|
||||||
|
Map<Path, ClientProfile> newProfiles = new HashMap<>();
|
||||||
|
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
|
||||||
|
Set<ClientProfile> newProfilesList = new HashSet<>(newProfiles.values());
|
||||||
|
profilesMap = newProfiles;
|
||||||
|
profilesList = newProfilesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ClientProfile> getProfiles() {
|
||||||
|
return profilesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addProfile(ClientProfile profile) throws IOException {
|
||||||
|
Path profilesDirPath = Path.of(profilesDir);
|
||||||
|
ClientProfile oldProfile;
|
||||||
|
Path target = null;
|
||||||
|
for(var e : profilesMap.entrySet()) {
|
||||||
|
if(e.getValue().getUUID().equals(profile.getUUID())) {
|
||||||
|
target = e.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(target == null) {
|
||||||
|
target = profilesDirPath.resolve(profile.getTitle()+".json");
|
||||||
|
oldProfile = profilesMap.get(target);
|
||||||
|
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
|
||||||
|
throw new FileAlreadyExistsException(target.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (BufferedWriter writer = IOHelper.newWriter(target)) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(profile, writer);
|
||||||
|
}
|
||||||
|
addProfile(target, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteProfile(ClientProfile profile) throws IOException {
|
||||||
|
for(var e : profilesMap.entrySet()) {
|
||||||
|
if(e.getValue().getUUID().equals(profile.getUUID())) {
|
||||||
|
Files.deleteIfExists(e.getKey());
|
||||||
|
profilesMap.remove(e.getKey());
|
||||||
|
profilesList.remove(e.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProfile(Path path, ClientProfile profile) {
|
||||||
|
for(var e : profilesMap.entrySet()) {
|
||||||
|
if(e.getValue().getUUID().equals(profile.getUUID())) {
|
||||||
|
profilesMap.remove(e.getKey());
|
||||||
|
profilesList.remove(e.getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profilesMap.put(path, profile);
|
||||||
|
profilesList.add(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
private final Map<Path, ClientProfile> result;
|
||||||
|
private final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
|
private ProfilesFileVisitor(Map<Path, ClientProfile> result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
|
||||||
|
|
||||||
|
// Read profile
|
||||||
|
ClientProfile profile;
|
||||||
|
try (BufferedReader reader = IOHelper.newReader(file)) {
|
||||||
|
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
|
||||||
|
}
|
||||||
|
profile.verify();
|
||||||
|
|
||||||
|
// Add SIGNED profile to result list
|
||||||
|
result.put(file.toAbsolutePath(), profile);
|
||||||
|
return super.visitFile(file, attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package pro.gravit.launchserver.auth.profiles;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class ProfileProvider {
|
||||||
|
public static final ProviderMap<ProfileProvider> providers = new ProviderMap<>("ProfileProvider");
|
||||||
|
private static boolean registredProviders = false;
|
||||||
|
protected transient LaunchServer server;
|
||||||
|
|
||||||
|
public static void registerProviders() {
|
||||||
|
if (!registredProviders) {
|
||||||
|
providers.register("local", LocalProfileProvider.class);
|
||||||
|
registredProviders = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(LaunchServer server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void sync() throws IOException;
|
||||||
|
|
||||||
|
public abstract Set<ClientProfile> getProfiles();
|
||||||
|
|
||||||
|
public abstract void addProfile(ClientProfile profile) throws IOException;
|
||||||
|
|
||||||
|
public abstract void deleteProfile(ClientProfile profile) throws IOException;
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientProfile getProfile(UUID uuid) {
|
||||||
|
for(var e : getProfiles()) {
|
||||||
|
if(e.getUUID().equals(uuid)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientProfile getProfile(String title) {
|
||||||
|
for(var e : getProfiles()) {
|
||||||
|
if(e.getTitle().equals(title)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClientProfile> getProfiles(Client client) {
|
||||||
|
List<ClientProfile> profileList;
|
||||||
|
Set<ClientProfile> serverProfiles = getProfiles();
|
||||||
|
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
|
||||||
|
profileList = new ArrayList<>(4);
|
||||||
|
for (ClientProfile profile : serverProfiles) {
|
||||||
|
if (protectHandler.canGetProfile(profile, client)) {
|
||||||
|
profileList.add(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
profileList = List.copyOf(serverProfiles);
|
||||||
|
}
|
||||||
|
return profileList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,9 @@
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
|
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
|
||||||
import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
|
import pro.gravit.launcher.base.events.request.HardwareReportRequestEvent;
|
||||||
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
|
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
|
||||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||||
|
@ -16,28 +15,20 @@
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler;
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
|
||||||
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
||||||
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
|
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
|
||||||
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
|
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
public boolean enableHardwareFeature;
|
public boolean enableHardwareFeature;
|
||||||
private transient LaunchServer server;
|
private transient LaunchServer server;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
|
||||||
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkLaunchServerLicense() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
|
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
|
||||||
return event;
|
return event;
|
||||||
|
@ -51,13 +42,17 @@ public boolean allowGetSecureLevelInfo(Client client) {
|
||||||
@Override
|
@Override
|
||||||
public void onHardwareReport(HardwareReportResponse response, Client client) {
|
public void onHardwareReport(HardwareReportResponse response, Client client) {
|
||||||
if (!enableHardwareFeature) {
|
if (!enableHardwareFeature) {
|
||||||
response.sendResult(new HardwareReportRequestEvent(null));
|
response.sendResult(new HardwareReportRequestEvent());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) {
|
if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) {
|
||||||
response.sendError("Access denied");
|
response.sendError("Access denied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(client.trustLevel.hardwareInfo != null) {
|
||||||
|
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, client.trustLevel.hardwareInfo), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.debug("HardwareInfo received");
|
logger.debug("HardwareInfo received");
|
||||||
{
|
{
|
||||||
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
|
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
|
||||||
|
@ -72,13 +67,11 @@ public void onHardwareReport(HardwareReportResponse response, Client client) {
|
||||||
if (hardware.isBanned()) {
|
if (hardware.isBanned()) {
|
||||||
throw new SecurityException("Your hardware banned");
|
throw new SecurityException("Your hardware banned");
|
||||||
}
|
}
|
||||||
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
|
client.trustLevel.hardwareInfo = hardware;
|
||||||
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware)));
|
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
logger.error("AuthCoreProvider not supported hardware");
|
logger.error("AuthCoreProvider not supported hardware");
|
||||||
response.sendError("AuthCoreProvider not supported hardware");
|
response.sendError("AuthCoreProvider not supported hardware");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,22 +83,22 @@ public VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
|
||||||
if (authSupportHardware != null) {
|
if (authSupportHardware != null) {
|
||||||
UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey);
|
UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey);
|
||||||
if (hardware == null) //HWID not found?
|
if (hardware == null) //HWID not found?
|
||||||
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
|
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
|
||||||
if (hardware.isBanned()) {
|
if (hardware.isBanned()) {
|
||||||
throw new SecurityException("Your hardware banned");
|
throw new SecurityException("Your hardware banned");
|
||||||
}
|
}
|
||||||
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
|
client.trustLevel.hardwareInfo = hardware;
|
||||||
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
|
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
|
||||||
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), createHardwareToken(client.username, hardware));
|
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
|
||||||
} else {
|
} else {
|
||||||
logger.warn("AuthCoreProvider not supported hardware. HardwareInfo not checked!");
|
logger.warn("AuthCoreProvider not supported hardware. HardwareInfo not checked!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
|
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onJoinServer(String serverID, String username, Client client) {
|
public boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
|
||||||
return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null);
|
return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,15 +107,11 @@ public void init(LaunchServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public String createHardwareToken(String username, UserHardware hardware) {
|
public String createHardwareToken(String username, UserHardware hardware) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setIssuer("LaunchServer")
|
.setIssuer("LaunchServer")
|
||||||
.setSubject(username)
|
.setSubject(username)
|
||||||
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
|
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)))
|
||||||
.claim("hardware", hardware.getId())
|
.claim("hardware", hardware.getId())
|
||||||
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
|
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
|
||||||
.compact();
|
.compact();
|
||||||
|
@ -132,22 +121,20 @@ public String createPublicKeyToken(String username, byte[] publicKey) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setIssuer("LaunchServer")
|
.setIssuer("LaunchServer")
|
||||||
.setSubject(username)
|
.setSubject(username)
|
||||||
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
|
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)))
|
||||||
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
|
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
|
||||||
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
|
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
|
public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
|
||||||
private transient final LaunchServer server;
|
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
private final JwtParser parser;
|
private final JwtParser parser;
|
||||||
|
|
||||||
public HardwareInfoTokenVerifier(LaunchServer server) {
|
public HardwareInfoTokenVerifier(LaunchServer server) {
|
||||||
this.server = server;
|
this.parser = Jwts.parser()
|
||||||
this.parser = Jwts.parserBuilder()
|
|
||||||
.requireIssuer("LaunchServer")
|
.requireIssuer("LaunchServer")
|
||||||
.setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
|
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,12 +144,12 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
|
||||||
var parse = parser.parseClaimsJws(extendedToken);
|
var parse = parser.parseClaimsJws(extendedToken);
|
||||||
String hardwareInfoId = parse.getBody().get("hardware", String.class);
|
String hardwareInfoId = parse.getBody().get("hardware", String.class);
|
||||||
if (hardwareInfoId == null) return false;
|
if (hardwareInfoId == null) return false;
|
||||||
if(client.auth == null) return false;
|
if (client.auth == null) return false;
|
||||||
var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class);
|
var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class);
|
||||||
if(hardwareSupport == null) return false;
|
if (hardwareSupport == null) return false;
|
||||||
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
|
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
|
||||||
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
|
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
|
||||||
client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
|
client.trustLevel.hardwareInfo = hardware;
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("Hardware JWT error", e);
|
logger.error("Hardware JWT error", e);
|
||||||
|
@ -173,15 +160,13 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
|
public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
|
||||||
private transient final LaunchServer server;
|
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
private final JwtParser parser;
|
private final JwtParser parser;
|
||||||
|
|
||||||
public PublicKeyTokenVerifier(LaunchServer server) {
|
public PublicKeyTokenVerifier(LaunchServer server) {
|
||||||
this.server = server;
|
this.parser = Jwts.parser()
|
||||||
this.parser = Jwts.parserBuilder()
|
|
||||||
.requireIssuer("LaunchServer")
|
.requireIssuer("LaunchServer")
|
||||||
.setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
|
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.protect;
|
package pro.gravit.launchserver.auth.protect;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
|
||||||
public class NoProtectHandler extends ProtectHandler {
|
public class NoProtectHandler extends ProtectHandler {
|
||||||
|
@ -10,7 +11,7 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkLaunchServerLicense() {
|
public boolean allowJoinServer(Client client) {
|
||||||
// None
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package pro.gravit.launchserver.auth.protect;
|
package pro.gravit.launchserver.auth.protect;
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
import pro.gravit.utils.ProviderMap;
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
|
@ -19,8 +20,9 @@ public static void registerHandlers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
|
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
|
||||||
|
public boolean allowJoinServer(Client client) {
|
||||||
public abstract void checkLaunchServerLicense(); //Выдает SecurityException при ошибке проверки лицензии
|
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
public void init(LaunchServer server) {
|
public void init(LaunchServer server) {
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
@ -20,26 +20,21 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||||
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkLaunchServerLicense() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(LaunchServer server) {
|
public void init(LaunchServer server) {
|
||||||
if(profileWhitelist != null && profileWhitelist.size() > 0) {
|
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
|
||||||
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
|
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canGetProfile(ClientProfile profile, Client client) {
|
public boolean canGetProfile(ClientProfile profile, Client client) {
|
||||||
return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.show", profile, client);
|
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canChangeProfile(ClientProfile profile, Client client) {
|
public boolean canChangeProfile(ClientProfile profile, Client client) {
|
||||||
return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.enter", profile, client);
|
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,18 +43,17 @@ public boolean canGetUpdates(String updatesDirName, Client client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
|
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
|
||||||
if(client.permissions != null) {
|
if (client.permissions != null) {
|
||||||
String permByUUID = String.format(property, profile.getUUID());
|
String permByUUID = property.formatted(profile.getUUID());
|
||||||
if(client.permissions.hasPerm(permByUUID)) {
|
if (client.permissions.hasPerm(permByUUID)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String permByTitle = String.format(property, profile.getTitle().toLowerCase(Locale.ROOT));
|
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
|
||||||
if(client.permissions.hasPerm(permByTitle)) {
|
if (client.permissions.hasPerm(permByTitle)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
|
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
|
||||||
if (allowedUsername != null && allowedUsername.contains(client.username)) return true;
|
return allowedUsername != null && allowedUsername.contains(client.username);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface JoinServerProtectHandler {
|
public interface JoinServerProtectHandler {
|
||||||
default boolean onJoinServer(String serverID, String username, Client client) {
|
default boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.protect.interfaces;
|
package pro.gravit.launchserver.auth.protect.interfaces;
|
||||||
|
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
public interface ProfilesProtectHandler {
|
public interface ProfilesProtectHandler {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package pro.gravit.launchserver.auth.protect.interfaces;
|
package pro.gravit.launchserver.auth.protect.interfaces;
|
||||||
|
|
||||||
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
|
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
|
||||||
import pro.gravit.launcher.events.request.SecurityReportRequestEvent;
|
import pro.gravit.launcher.base.events.request.SecurityReportRequestEvent;
|
||||||
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
|
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
|
@ -3,35 +3,37 @@
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.HTTPRequest;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launchserver.HttpRequester;
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class JsonTextureProvider extends TextureProvider {
|
public class JsonTextureProvider extends TextureProvider {
|
||||||
public String url;
|
private static final Type MAP_TYPE = new TypeToken<Map<String, JsonTexture>>() {
|
||||||
|
}.getType();
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {}.getType();
|
private transient final HttpRequester requester = new HttpRequester();
|
||||||
|
public String url;
|
||||||
|
public String bearerToken;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() {
|
||||||
//None
|
//None
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Texture getCloakTexture(UUID uuid, String username, String client) throws IOException {
|
public Texture getCloakTexture(UUID uuid, String username, String client) {
|
||||||
logger.warn("Ineffective get cloak texture for {}", username);
|
logger.warn("Ineffective get cloak texture for {}", username);
|
||||||
return getAssets(uuid, username, client).get("CAPE");
|
return getAssets(uuid, username, client).get("CAPE");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Texture getSkinTexture(UUID uuid, String username, String client) throws IOException {
|
public Texture getSkinTexture(UUID uuid, String username, String client) {
|
||||||
logger.warn("Ineffective get skin texture for {}", username);
|
logger.warn("Ineffective get skin texture for {}", username);
|
||||||
return getAssets(uuid, username, client).get("SKIN");
|
return getAssets(uuid, username, client).get("SKIN");
|
||||||
}
|
}
|
||||||
|
@ -39,24 +41,28 @@ public Texture getSkinTexture(UUID uuid, String username, String client) throws
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
|
public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
|
||||||
try {
|
try {
|
||||||
var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client)));
|
Map<String, JsonTexture> map = requester.<Map<String, JsonTexture>>send(requester.get(RequestTextureProvider.getTextureURL(url, uuid, username, client), bearerToken), MAP_TYPE).getOrThrow();
|
||||||
|
return JsonTexture.convertMap(map);
|
||||||
Map<String, Texture> map = Launcher.gsonManager.gson.fromJson(result, MAP_TYPE);
|
|
||||||
if(map == null) {
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
if(map.get("skin") != null) { // Legacy script
|
|
||||||
map.put("SKIN", map.get("skin"));
|
|
||||||
map.remove("skin");
|
|
||||||
}
|
|
||||||
if(map.get("cloak") != null) {
|
|
||||||
map.put("CAPE", map.get("cloak"));
|
|
||||||
map.remove("cloak");
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("JsonTextureProvider", e);
|
logger.error("JsonTextureProvider", e);
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record JsonTexture(String url, String digest, Map<String, String> metadata) {
|
||||||
|
public Texture toTexture() {
|
||||||
|
return new Texture(url, digest == null ? null : SecurityHelper.fromHex(digest), metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Texture> convertMap(Map<String, JsonTexture> map) {
|
||||||
|
if (map == null) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
Map<String, Texture> res = new HashMap<>();
|
||||||
|
for(var e : map.entrySet()) {
|
||||||
|
res.put(e.getKey(), e.getValue().toTexture());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.texture;
|
package pro.gravit.launchserver.auth.texture;
|
||||||
|
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
import pro.gravit.utils.helper.VerifyHelper;
|
import pro.gravit.utils.helper.VerifyHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pro.gravit.launchserver.auth.texture;
|
package pro.gravit.launchserver.auth.texture;
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ public RequestTextureProvider(String skinURL, String cloakURL) {
|
||||||
|
|
||||||
private static Texture getTexture(String url, boolean cloak) throws IOException {
|
private static Texture getTexture(String url, boolean cloak) throws IOException {
|
||||||
try {
|
try {
|
||||||
return new Texture(url, cloak);
|
return new Texture(url, cloak, null);
|
||||||
} catch (FileNotFoundException ignored) {
|
} catch (FileNotFoundException ignored) {
|
||||||
return null; // Simply not found
|
return null; // Simply not found
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ private static Texture getTexture(String url, boolean cloak) throws IOException
|
||||||
|
|
||||||
private static Texture getTexture(String url, Path local, boolean cloak) throws IOException {
|
private static Texture getTexture(String url, Path local, boolean cloak) throws IOException {
|
||||||
try {
|
try {
|
||||||
return new Texture(url, local, cloak);
|
return new Texture(url, local, cloak, null);
|
||||||
} catch (FileNotFoundException ignored) {
|
} catch (FileNotFoundException ignored) {
|
||||||
return null; // Simply not found
|
return null; // Simply not found
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,8 @@ public Texture getCloakTexture(UUID uuid, String username, String client) throws
|
||||||
if (cloakLocalPath == null) {
|
if (cloakLocalPath == null) {
|
||||||
return getTexture(textureUrl, true);
|
return getTexture(textureUrl, true);
|
||||||
} else {
|
} else {
|
||||||
return getTexture(textureUrl, Paths.get(cloakLocalPath), true);
|
String path = getTextureURL(cloakLocalPath, uuid, username, client);
|
||||||
|
return getTexture(textureUrl, Paths.get(path), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +71,8 @@ public Texture getSkinTexture(UUID uuid, String username, String client) throws
|
||||||
if (skinLocalPath == null) {
|
if (skinLocalPath == null) {
|
||||||
return getTexture(textureUrl, false);
|
return getTexture(textureUrl, false);
|
||||||
} else {
|
} else {
|
||||||
return getTexture(textureUrl, Paths.get(skinLocalPath), false);
|
String path = getTextureURL(skinLocalPath, uuid, username, client);
|
||||||
|
return getTexture(textureUrl, Paths.get(path), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.texture;
|
package pro.gravit.launchserver.auth.texture;
|
||||||
|
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
import pro.gravit.utils.ProviderMap;
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -33,17 +33,6 @@ public static void registerProviders() {
|
||||||
|
|
||||||
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
|
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static class SkinAndCloakTextures {
|
|
||||||
public final Texture skin;
|
|
||||||
public final Texture cloak;
|
|
||||||
|
|
||||||
public SkinAndCloakTextures(Texture skin, Texture cloak) {
|
|
||||||
this.skin = skin;
|
|
||||||
this.cloak = cloak;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
|
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
|
||||||
|
|
||||||
|
@ -83,13 +72,24 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Texture> map = new HashMap<>();
|
Map<String, Texture> map = new HashMap<>();
|
||||||
if(skin != null) {
|
if (skin != null) {
|
||||||
map.put("SKIN", skin);
|
map.put("SKIN", skin);
|
||||||
}
|
}
|
||||||
if(cloak != null) {
|
if (cloak != null) {
|
||||||
map.put("CAPE", cloak);
|
map.put("CAPE", cloak);
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static class SkinAndCloakTextures {
|
||||||
|
public final Texture skin;
|
||||||
|
public final Texture cloak;
|
||||||
|
|
||||||
|
public SkinAndCloakTextures(Texture skin, Texture cloak) {
|
||||||
|
this.skin = skin;
|
||||||
|
this.cloak = cloak;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.auth.texture;
|
package pro.gravit.launchserver.auth.texture;
|
||||||
|
|
||||||
import pro.gravit.launcher.profiles.Texture;
|
import pro.gravit.launcher.base.profiles.Texture;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package pro.gravit.launchserver.auth.updates;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||||
|
import pro.gravit.launcher.core.serialize.HInput;
|
||||||
|
import pro.gravit.launcher.core.serialize.HOutput;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
|
||||||
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class LocalUpdatesProvider extends UpdatesProvider {
|
||||||
|
private final transient Logger logger = LogManager.getLogger();
|
||||||
|
public String cacheFile = ".updates-cache";
|
||||||
|
public String updatesDir = "updates";
|
||||||
|
public boolean cacheUpdates = true;
|
||||||
|
private volatile transient Map<String, HashedDir> updatesDirMap;
|
||||||
|
|
||||||
|
private void writeCache(Path file) throws IOException {
|
||||||
|
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
|
||||||
|
output.writeLength(updatesDirMap.size(), 0);
|
||||||
|
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
|
||||||
|
output.writeString(entry.getKey(), 0);
|
||||||
|
entry.getValue().write(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Saved {} updates to cache", updatesDirMap.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readCache(Path file) throws IOException {
|
||||||
|
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
|
||||||
|
try (HInput input = new HInput(IOHelper.newInput(file))) {
|
||||||
|
int size = input.readLength(0);
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
String name = input.readString(0);
|
||||||
|
HashedDir dir = new HashedDir(input);
|
||||||
|
updatesDirMap.put(name, dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Found {} updates from cache", updatesDirMap.size());
|
||||||
|
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readUpdatesFromCache() throws IOException {
|
||||||
|
readCache(Path.of(cacheFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readUpdatesDir() throws IOException {
|
||||||
|
var cacheFilePath = Path.of(cacheFile);
|
||||||
|
if (cacheUpdates) {
|
||||||
|
if (Files.exists(cacheFilePath)) {
|
||||||
|
try {
|
||||||
|
readCache(cacheFilePath);
|
||||||
|
return;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("Read updates cache failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer server) {
|
||||||
|
super.init(server);
|
||||||
|
try {
|
||||||
|
if (!IOHelper.isDir(Path.of(updatesDir)))
|
||||||
|
Files.createDirectory(Path.of(updatesDir));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Updates not synced", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void syncInitially() throws IOException {
|
||||||
|
readUpdatesDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(Collection<String> dirs) throws IOException {
|
||||||
|
logger.info("Syncing updates dir");
|
||||||
|
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
|
||||||
|
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
|
||||||
|
for (final Path updateDir : dirStream) {
|
||||||
|
if (Files.isHidden(updateDir))
|
||||||
|
continue; // Skip hidden
|
||||||
|
|
||||||
|
// Resolve name and verify is dir
|
||||||
|
String name = IOHelper.getFileName(updateDir);
|
||||||
|
if (!IOHelper.isDir(updateDir)) {
|
||||||
|
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
|
||||||
|
logger.warn("Not update dir: '{}'", name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add from previous map (it's guaranteed to be non-null)
|
||||||
|
if (dirs != null && !dirs.contains(name)) {
|
||||||
|
HashedDir hdir = updatesDirMap.get(name);
|
||||||
|
if (hdir != null) {
|
||||||
|
newUpdatesDirMap.put(name, hdir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync and sign update dir
|
||||||
|
logger.info("Syncing '{}' update dir", name);
|
||||||
|
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
|
||||||
|
newUpdatesDirMap.put(name, updateHDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
|
||||||
|
if (cacheUpdates) {
|
||||||
|
try {
|
||||||
|
writeCache(Path.of(cacheFile));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("Write updates cache failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashedDir getUpdatesDir(String updateName) {
|
||||||
|
return updatesDirMap.get(updateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path resolveUpdateName(String updateName) {
|
||||||
|
if(updateName == null) {
|
||||||
|
return Path.of(updatesDir);
|
||||||
|
}
|
||||||
|
return Path.of(updatesDir).resolve(updateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
|
||||||
|
var path = resolveUpdateName(updateName);
|
||||||
|
for(var e : files.entrySet()) {
|
||||||
|
var target = path.resolve(e.getKey());
|
||||||
|
var source = e.getValue();
|
||||||
|
IOHelper.createParentDirs(target);
|
||||||
|
if(deleteAfterUpload) {
|
||||||
|
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} else {
|
||||||
|
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Path> download(String updateName, List<String> files) {
|
||||||
|
var path = resolveUpdateName(updateName);
|
||||||
|
Map<String, Path> map = new HashMap<>();
|
||||||
|
for(var e : files) {
|
||||||
|
map.put(e, path.resolve(e));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String updateName, List<String> files) throws IOException {
|
||||||
|
var path = resolveUpdateName(updateName);
|
||||||
|
for(var e : files) {
|
||||||
|
var target = path.resolve(e);
|
||||||
|
Files.delete(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String updateName) throws IOException {
|
||||||
|
var path = resolveUpdateName(updateName);
|
||||||
|
IOHelper.deleteDir(path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(String updateName) throws IOException {
|
||||||
|
var path = resolveUpdateName(updateName);
|
||||||
|
Files.createDirectories(path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package pro.gravit.launchserver.auth.updates;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.core.hasher.HashedDir;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class UpdatesProvider {
|
||||||
|
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
|
||||||
|
private static boolean registredProviders = false;
|
||||||
|
protected transient LaunchServer server;
|
||||||
|
|
||||||
|
public static void registerProviders() {
|
||||||
|
if (!registredProviders) {
|
||||||
|
providers.register("local", LocalUpdatesProvider.class);
|
||||||
|
registredProviders = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(LaunchServer server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync() throws IOException {
|
||||||
|
sync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void syncInitially() throws IOException;
|
||||||
|
|
||||||
|
public abstract void sync(Collection<String> updateNames) throws IOException;
|
||||||
|
|
||||||
|
public abstract HashedDir getUpdatesDir(String updateName);
|
||||||
|
|
||||||
|
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
|
||||||
|
|
||||||
|
public abstract Map<String, Path> download(String updateName, List<String> files);
|
||||||
|
|
||||||
|
public abstract void delete(String updateName, List<String> files) throws IOException;
|
||||||
|
|
||||||
|
public abstract void delete(String updateName) throws IOException;
|
||||||
|
|
||||||
|
public abstract void create(String updateName) throws IOException;
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,24 +4,19 @@
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
||||||
import pro.gravit.utils.helper.CommonHelper;
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class BinaryPipeline {
|
public abstract class BinaryPipeline {
|
||||||
public final List<LauncherBuildTask> tasks = new ArrayList<>();
|
public final List<LauncherBuildTask> tasks = new ArrayList<>();
|
||||||
public final AtomicLong count = new AtomicLong(0);
|
|
||||||
public final Path buildDir;
|
public final Path buildDir;
|
||||||
public final String nameFormat;
|
public final String nameFormat;
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
protected transient final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
public BinaryPipeline(Path buildDir, String nameFormat) {
|
public BinaryPipeline(Path buildDir, String nameFormat) {
|
||||||
this.buildDir = buildDir;
|
this.buildDir = buildDir;
|
||||||
|
@ -72,33 +67,19 @@ public <T extends LauncherBuildTask> Optional<T> getTaskByClass(Class<T> taskCla
|
||||||
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst();
|
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(Path target, boolean deleteTempFiles) throws IOException {
|
public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pred) {
|
||||||
logger.info("Building launcher binary file");
|
LauncherBuildTask last = null;
|
||||||
count.set(0); // set jar number
|
for(var e : tasks) {
|
||||||
Path thisPath = null;
|
if(pred.test(e)) {
|
||||||
boolean isNeedDelete = false;
|
return Optional.ofNullable(last);
|
||||||
long time_start = System.currentTimeMillis();
|
}
|
||||||
long time_this = time_start;
|
last = e;
|
||||||
for (LauncherBuildTask task : tasks) {
|
|
||||||
logger.info("Task {}", task.getName());
|
|
||||||
Path oldPath = thisPath;
|
|
||||||
thisPath = task.process(oldPath);
|
|
||||||
long time_task_end = System.currentTimeMillis();
|
|
||||||
long time_task = time_task_end - time_this;
|
|
||||||
time_this = time_task_end;
|
|
||||||
if (isNeedDelete && deleteTempFiles) Files.deleteIfExists(oldPath);
|
|
||||||
isNeedDelete = task.allowDelete();
|
|
||||||
logger.info("Task {} processed from {} millis", task.getName(), time_task);
|
|
||||||
}
|
}
|
||||||
long time_end = System.currentTimeMillis();
|
return Optional.empty();
|
||||||
if (isNeedDelete && deleteTempFiles) IOHelper.move(thisPath, target);
|
|
||||||
else IOHelper.copy(thisPath, target);
|
|
||||||
IOHelper.deleteDir(buildDir, false);
|
|
||||||
logger.info("Build successful from {} millis", time_end - time_start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String nextName(String taskName) {
|
public String nextName(String taskName) {
|
||||||
return String.format(nameFormat, taskName, count.getAndIncrement());
|
return nameFormat.formatted(taskName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path nextPath(String taskName) {
|
public Path nextPath(String taskName) {
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.serialize.HOutput;
|
import pro.gravit.launcher.core.serialize.HOutput;
|
||||||
import pro.gravit.launcher.serialize.stream.StreamObject;
|
import pro.gravit.launcher.core.serialize.stream.StreamObject;
|
||||||
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
@ -45,13 +45,18 @@ public class BuildContext {
|
||||||
public final MainBuildTask task;
|
public final MainBuildTask task;
|
||||||
public final HashSet<String> fileList;
|
public final HashSet<String> fileList;
|
||||||
public final HashSet<String> clientModules;
|
public final HashSet<String> clientModules;
|
||||||
|
public final HashSet<String> legacyClientModules;
|
||||||
|
private Path runtimeDir;
|
||||||
|
private boolean deleteRuntimeDir;
|
||||||
|
|
||||||
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task) {
|
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
this.readerClassPath = readerClassPath;
|
this.readerClassPath = readerClassPath;
|
||||||
this.task = task;
|
this.task = task;
|
||||||
|
this.runtimeDir = runtimeDir;
|
||||||
fileList = new HashSet<>(1024);
|
fileList = new HashSet<>(1024);
|
||||||
clientModules = new HashSet<>();
|
clientModules = new HashSet<>();
|
||||||
|
legacyClientModules = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushFile(String filename, InputStream inputStream) throws IOException {
|
public void pushFile(String filename, InputStream inputStream) throws IOException {
|
||||||
|
@ -101,6 +106,14 @@ public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<Stri
|
||||||
pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
|
pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getRuntimeDir() {
|
||||||
|
return runtimeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRuntimeDir(Path runtimeDir) {
|
||||||
|
this.runtimeDir = runtimeDir;
|
||||||
|
}
|
||||||
|
|
||||||
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
|
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
|
||||||
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
|
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
|
||||||
ZipEntry e = input.getNextEntry();
|
ZipEntry e = input.getNextEntry();
|
||||||
|
@ -127,6 +140,16 @@ public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<Strin
|
||||||
e = input.getNextEntry();
|
e = input.getNextEntry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleteRuntimeDir() {
|
||||||
|
return deleteRuntimeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteRuntimeDir(boolean deleteRuntimeDir) {
|
||||||
|
this.deleteRuntimeDir = deleteRuntimeDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
|
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
|
||||||
|
@ -211,7 +234,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
||||||
try {
|
try {
|
||||||
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
|
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
||||||
|
InvalidAlgorithmParameterException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {
|
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package pro.gravit.launchserver.binary;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.binary.tasks.exe.Launch4JTask;
|
|
||||||
|
|
||||||
public final class EXEL4JLauncherBinary extends LauncherBinary {
|
|
||||||
|
|
||||||
|
|
||||||
public EXEL4JLauncherBinary(LaunchServer server) {
|
|
||||||
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() {
|
|
||||||
tasks.add(new Launch4JTask(server));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@
|
||||||
public class EXELauncherBinary extends LauncherBinary {
|
public class EXELauncherBinary extends LauncherBinary {
|
||||||
|
|
||||||
public EXELauncherBinary(LaunchServer server) {
|
public EXELauncherBinary(LaunchServer server) {
|
||||||
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
|
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pro.gravit.launchserver.binary;
|
package pro.gravit.launchserver.binary;
|
||||||
|
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.binary.tasks.*;
|
import pro.gravit.launchserver.binary.tasks.*;
|
||||||
|
|
||||||
|
@ -8,25 +8,28 @@
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
public final class JARLauncherBinary extends LauncherBinary {
|
public final class JARLauncherBinary extends LauncherBinary {
|
||||||
public final AtomicLong count;
|
public final AtomicLong count;
|
||||||
public final Path runtimeDir;
|
public final Path runtimeDir;
|
||||||
public final Path guardDir;
|
|
||||||
public final Path buildDir;
|
public final Path buildDir;
|
||||||
public final List<Path> coreLibs;
|
public final List<Path> coreLibs;
|
||||||
public final List<Path> addonLibs;
|
public final List<Path> addonLibs;
|
||||||
|
|
||||||
|
public final Map<String, Path> files;
|
||||||
|
|
||||||
public JARLauncherBinary(LaunchServer server) throws IOException {
|
public JARLauncherBinary(LaunchServer server) throws IOException {
|
||||||
super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar");
|
super(server, resolve(server, ".jar"), "Launcher-%s.jar");
|
||||||
count = new AtomicLong(0);
|
count = new AtomicLong(0);
|
||||||
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
|
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
|
||||||
guardDir = server.dir.resolve(Launcher.GUARD_DIR);
|
|
||||||
buildDir = server.dir.resolve("build");
|
buildDir = server.dir.resolve("build");
|
||||||
coreLibs = new ArrayList<>();
|
coreLibs = new ArrayList<>();
|
||||||
addonLibs = new ArrayList<>();
|
addonLibs = new ArrayList<>();
|
||||||
|
files = new HashMap<>();
|
||||||
if (!Files.isDirectory(buildDir)) {
|
if (!Files.isDirectory(buildDir)) {
|
||||||
Files.deleteIfExists(buildDir);
|
Files.deleteIfExists(buildDir);
|
||||||
Files.createDirectory(buildDir);
|
Files.createDirectory(buildDir);
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package pro.gravit.launchserver.binary;
|
package pro.gravit.launchserver.binary;
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class LauncherBinary extends BinaryPipeline {
|
public abstract class LauncherBinary extends BinaryPipeline {
|
||||||
public final LaunchServer server;
|
public final LaunchServer server;
|
||||||
|
@ -19,11 +22,27 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path resolve(LaunchServer server, String ext) {
|
public static Path resolve(LaunchServer server, String ext) {
|
||||||
return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
|
return Path.of(server.config.binaryName + ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build() throws IOException {
|
public void build() throws IOException {
|
||||||
build(syncBinaryFile, server.config.launcher.deleteTempFiles);
|
logger.info("Building launcher binary file");
|
||||||
|
Path thisPath = null;
|
||||||
|
long time_start = System.currentTimeMillis();
|
||||||
|
long time_this = time_start;
|
||||||
|
for (LauncherBuildTask task : tasks) {
|
||||||
|
logger.info("Task {}", task.getName());
|
||||||
|
Path oldPath = thisPath;
|
||||||
|
thisPath = task.process(oldPath);
|
||||||
|
long time_task_end = System.currentTimeMillis();
|
||||||
|
long time_task = time_task_end - time_this;
|
||||||
|
time_this = time_task_end;
|
||||||
|
logger.info("Task {} processed from {} millis", task.getName(), time_task);
|
||||||
|
}
|
||||||
|
long time_end = System.currentTimeMillis();
|
||||||
|
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true);
|
||||||
|
IOHelper.deleteDir(buildDir, false);
|
||||||
|
logger.info("Build successful from {} millis", time_end - time_start);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean exists() {
|
public final boolean exists() {
|
||||||
|
@ -37,10 +56,14 @@ public final byte[] getDigest() {
|
||||||
public void init() {
|
public void init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean sync() throws IOException {
|
public final boolean sync() {
|
||||||
boolean exists = exists();
|
try {
|
||||||
digest = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(syncBinaryFile)) : null;
|
var target = syncBinaryFile.toString();
|
||||||
|
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
|
||||||
return exists;
|
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
|
||||||
|
return true;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,6 @@ public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, Stri
|
||||||
*
|
*
|
||||||
* @param filename name of the file to add (use forward slash as a path separator)
|
* @param filename name of the file to add (use forward slash as a path separator)
|
||||||
* @param contents contents of the file
|
* @param contents contents of the file
|
||||||
* @throws IOException
|
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(String filename, byte[] contents) throws IOException {
|
public void addFileContents(String filename, byte[] contents) throws IOException {
|
||||||
|
@ -82,7 +81,6 @@ public void addFileContents(String filename, byte[] contents) throws IOException
|
||||||
*
|
*
|
||||||
* @param filename name of the file to add (use forward slash as a path separator)
|
* @param filename name of the file to add (use forward slash as a path separator)
|
||||||
* @param contents contents of the file
|
* @param contents contents of the file
|
||||||
* @throws IOException
|
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(String filename, InputStream contents) throws IOException {
|
public void addFileContents(String filename, InputStream contents) throws IOException {
|
||||||
|
@ -95,7 +93,6 @@ public void addFileContents(String filename, InputStream contents) throws IOExce
|
||||||
*
|
*
|
||||||
* @param entry name of the file to add (use forward slash as a path separator)
|
* @param entry name of the file to add (use forward slash as a path separator)
|
||||||
* @param contents contents of the file
|
* @param contents contents of the file
|
||||||
* @throws IOException
|
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
|
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
|
||||||
|
@ -108,7 +105,6 @@ public void addFileContents(ZipEntry entry, byte[] contents) throws IOException
|
||||||
*
|
*
|
||||||
* @param entry name of the file to add (use forward slash as a path separator)
|
* @param entry name of the file to add (use forward slash as a path separator)
|
||||||
* @param contents contents of the file
|
* @param contents contents of the file
|
||||||
* @throws IOException
|
|
||||||
* @throws NullPointerException if any of the arguments is {@code null}
|
* @throws NullPointerException if any of the arguments is {@code null}
|
||||||
*/
|
*/
|
||||||
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
|
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
|
||||||
|
@ -134,7 +130,6 @@ public void addManifestAttribute(String name, String value) {
|
||||||
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
|
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
|
||||||
* underlying stream.
|
* underlying stream.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
|
||||||
* @throws RuntimeException if the signing goes wrong
|
* @throws RuntimeException if the signing goes wrong
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,7 +143,6 @@ public void close() throws IOException {
|
||||||
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
|
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
|
||||||
* underlying stream open.
|
* underlying stream open.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
|
||||||
* @throws RuntimeException if the signing goes wrong
|
* @throws RuntimeException if the signing goes wrong
|
||||||
*/
|
*/
|
||||||
public void finish() throws IOException {
|
public void finish() throws IOException {
|
||||||
|
@ -205,7 +199,6 @@ private byte[] signSigFile(byte[] sigContents) throws Exception {
|
||||||
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
|
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
|
||||||
* file.
|
* file.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
private void writeManifest() throws IOException {
|
private void writeManifest() throws IOException {
|
||||||
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
|
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
|
||||||
|
@ -268,7 +261,6 @@ private byte[] writeSigFile() throws IOException {
|
||||||
/**
|
/**
|
||||||
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
|
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
|
||||||
*
|
*
|
||||||
* @throws IOException
|
|
||||||
* @throws RuntimeException if the signing failed
|
* @throws RuntimeException if the signing failed
|
||||||
*/
|
*/
|
||||||
private void writeSignature(byte[] sigFile) throws IOException {
|
private void writeSignature(byte[] sigFile) throws IOException {
|
||||||
|
|
|
@ -54,7 +54,7 @@ public static void apply(Path inputFile, Path addFile, ZipOutputStream output, L
|
||||||
private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException {
|
private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException {
|
||||||
ClassReader cr = new ClassReader(input);
|
ClassReader cr = new ClassReader(input);
|
||||||
ClassNode cn = new ClassNode();
|
ClassNode cn = new ClassNode();
|
||||||
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) : ClassReader.SKIP_FRAMES);
|
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG) : 0);
|
||||||
ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||||
cn.accept(cw);
|
cn.accept(cw);
|
||||||
return cw.toByteArray();
|
return cw.toByteArray();
|
||||||
|
@ -74,9 +74,4 @@ public Path process(Path inputFile) throws IOException {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,11 @@ public Path process(Path inputFile) throws IOException {
|
||||||
}
|
}
|
||||||
attach(output, inputFile, srv.launcherBinary.coreLibs);
|
attach(output, inputFile, srv.launcherBinary.coreLibs);
|
||||||
attach(output, inputFile, jars);
|
attach(output, inputFile, jars);
|
||||||
|
for(var entry : srv.launcherBinary.files.entrySet()) {
|
||||||
|
ZipEntry newEntry = IOHelper.newZipEntry(entry.getKey());
|
||||||
|
output.putNextEntry(newEntry);
|
||||||
|
IOHelper.transfer(entry.getValue(), output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return outputFile;
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
@ -63,11 +68,6 @@ private boolean filter(String name) {
|
||||||
return exclusions.stream().anyMatch(name::startsWith);
|
return exclusions.stream().anyMatch(name::startsWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Path> getJars() {
|
public List<Path> getJars() {
|
||||||
return jars;
|
return jars;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,9 +81,4 @@ public Path process(Path inputFile) throws IOException {
|
||||||
}
|
}
|
||||||
return inputFile;
|
return inputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,4 @@ public Path process(Path inputFile) throws IOException {
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,4 @@ public interface LauncherBuildTask {
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
Path process(Path inputFile) throws IOException;
|
Path process(Path inputFile) throws IOException;
|
||||||
|
|
||||||
boolean allowDelete();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
import org.objectweb.asm.tree.AnnotationNode;
|
import org.objectweb.asm.tree.AnnotationNode;
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
import org.objectweb.asm.tree.FieldNode;
|
import org.objectweb.asm.tree.FieldNode;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.LauncherConfig;
|
import pro.gravit.launcher.base.LauncherConfig;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.asm.ClassMetadataReader;
|
import pro.gravit.launchserver.asm.ClassMetadataReader;
|
||||||
import pro.gravit.launchserver.asm.InjectClassAcceptor;
|
import pro.gravit.launchserver.asm.InjectClassAcceptor;
|
||||||
|
@ -51,11 +51,12 @@ public String getName() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path process(Path inputJar) throws IOException {
|
public Path process(Path inputJar) throws IOException {
|
||||||
Path outputJar = server.launcherBinary.nextPath("main");
|
Path outputJar = server.launcherBinary.nextPath(this);
|
||||||
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
|
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
|
||||||
BuildContext context = new BuildContext(output, reader.getCp(), this);
|
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir);
|
||||||
initProps();
|
initProps();
|
||||||
preBuildHook.hook(context);
|
preBuildHook.hook(context);
|
||||||
|
properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||||
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
|
||||||
postInitProps();
|
postInitProps();
|
||||||
reader.getCp().add(new JarFile(inputJar.toFile()));
|
reader.getCp().add(new JarFile(inputJar.toFile()));
|
||||||
|
@ -68,11 +69,13 @@ public Path process(Path inputJar) throws IOException {
|
||||||
Map<String, byte[]> runtime = new HashMap<>(256);
|
Map<String, byte[]> runtime = new HashMap<>(256);
|
||||||
// Write launcher guard dir
|
// Write launcher guard dir
|
||||||
if (server.config.launcher.encryptRuntime) {
|
if (server.config.launcher.encryptRuntime) {
|
||||||
context.pushEncryptedDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
|
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
|
||||||
} else {
|
} else {
|
||||||
context.pushDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, runtime, false);
|
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false);
|
||||||
|
}
|
||||||
|
if(context.isDeleteRuntimeDir()) {
|
||||||
|
IOHelper.deleteDir(context.getRuntimeDir(), true);
|
||||||
}
|
}
|
||||||
context.pushDir(server.launcherBinary.guardDir, Launcher.GUARD_DIR, runtime, false);
|
|
||||||
|
|
||||||
LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName);
|
LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName);
|
||||||
context.pushFile(Launcher.CONFIG_FILE, launcherConfig);
|
context.pushFile(Launcher.CONFIG_FILE, launcherConfig);
|
||||||
|
@ -108,7 +111,6 @@ protected void initProps() {
|
||||||
properties.put("launcher.projectName", server.config.projectName);
|
properties.put("launcher.projectName", server.config.projectName);
|
||||||
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
|
||||||
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
|
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
|
||||||
properties.put("launcher.guardType", server.config.launcher.guardType);
|
|
||||||
properties.put("launchercore.env", server.config.env);
|
properties.put("launchercore.env", server.config.env);
|
||||||
properties.put("launcher.memory", server.config.launcher.memoryLimit);
|
properties.put("launcher.memory", server.config.launcher.memoryLimit);
|
||||||
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
|
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
|
||||||
|
@ -124,10 +126,10 @@ protected void initProps() {
|
||||||
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
|
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
|
||||||
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
|
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
|
||||||
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
|
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
|
||||||
//LogHelper.debug("[checkSecure] %s: %s", launcherSalt, Arrays.toString(launcherSecureHash));
|
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
|
||||||
if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
|
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
|
||||||
properties.put("runtimeconfig.oemUnlockKey", server.runtime.oemUnlockKey);
|
server.runtime.buildNumber++;
|
||||||
|
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
|
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
|
||||||
|
@ -144,7 +146,7 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
|
||||||
asmTransformer.transform(cn, classname, context);
|
asmTransformer.transform(cn, classname, context);
|
||||||
continue;
|
continue;
|
||||||
} else if (cn != null) {
|
} else if (cn != null) {
|
||||||
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
writer = new SafeClassWriter(reader, 0);
|
||||||
cn.accept(writer);
|
cn.accept(writer);
|
||||||
result = writer.toByteArray();
|
result = writer.toByteArray();
|
||||||
}
|
}
|
||||||
|
@ -155,18 +157,13 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cn != null) {
|
if (cn != null) {
|
||||||
writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
writer = new SafeClassWriter(reader, 0);
|
||||||
cn.accept(writer);
|
cn.accept(writer);
|
||||||
result = writer.toByteArray();
|
result = writer.toByteArray();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface Transformer {
|
public interface Transformer {
|
||||||
byte[] transform(byte[] input, String classname, BuildContext context);
|
byte[] transform(byte[] input, String classname, BuildContext context);
|
||||||
|
@ -178,7 +175,7 @@ default byte[] transform(byte[] input, String classname, BuildContext context) {
|
||||||
ClassNode cn = new ClassNode();
|
ClassNode cn = new ClassNode();
|
||||||
reader.accept(cn, 0);
|
reader.accept(cn, 0);
|
||||||
transform(cn, classname, context);
|
transform(cn, classname, context);
|
||||||
SafeClassWriter writer = new SafeClassWriter(context.task.reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
SafeClassWriter writer = new SafeClassWriter(context.task.reader, 0);
|
||||||
cn.accept(writer);
|
cn.accept(writer);
|
||||||
return writer.toByteArray();
|
return writer.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
import pro.gravit.utils.helper.UnpackHelper;
|
import pro.gravit.utils.helper.UnpackHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.*;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class PrepareBuildTask implements LauncherBuildTask {
|
public class PrepareBuildTask implements LauncherBuildTask {
|
||||||
private final LaunchServer server;
|
private final LaunchServer server;
|
||||||
|
@ -33,21 +32,28 @@ public String getName() {
|
||||||
public Path process(Path inputFile) throws IOException {
|
public Path process(Path inputFile) throws IOException {
|
||||||
server.launcherBinary.coreLibs.clear();
|
server.launcherBinary.coreLibs.clear();
|
||||||
server.launcherBinary.addonLibs.clear();
|
server.launcherBinary.addonLibs.clear();
|
||||||
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), true);
|
server.launcherBinary.files.clear();
|
||||||
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), true);
|
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false);
|
||||||
|
if(Files.isDirectory(server.launcherLibrariesCompile)) {
|
||||||
|
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
|
||||||
|
}
|
||||||
|
try(Stream<Path> stream = Files.walk(server.launcherPack, FileVisitOption.FOLLOW_LINKS).filter((e) -> {
|
||||||
|
try {
|
||||||
|
return !Files.isDirectory(e) && !Files.isHidden(e);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString().replace("\\", "/"), (v) -> v));
|
||||||
|
server.launcherBinary.files.putAll(map);
|
||||||
|
}
|
||||||
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
|
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
|
||||||
tryUnpack();
|
tryUnpack();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryUnpack() throws IOException {
|
public void tryUnpack() throws IOException {
|
||||||
logger.info("Unpacking launcher native guard list and runtime");
|
logger.info("Unpacking launcher native guard list and runtime");
|
||||||
UnpackHelper.unpackZipNoCheck("guard.zip", server.launcherBinary.guardDir);
|
|
||||||
UnpackHelper.unpackZipNoCheck("runtime.zip", server.launcherBinary.runtimeDir);
|
UnpackHelper.unpackZipNoCheck("runtime.zip", server.launcherBinary.runtimeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
public class SignJarTask implements LauncherBuildTask {
|
public class SignJarTask implements LauncherBuildTask {
|
||||||
|
|
||||||
private transient static final Logger logger = LogManager.getLogger();
|
private static final Logger logger = LogManager.getLogger();
|
||||||
private final LaunchServerConfig.JarSignerConf config;
|
private final LaunchServerConfig.JarSignerConf config;
|
||||||
private final LaunchServer srv;
|
private final LaunchServer srv;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ public static CMSSignedDataGenerator gen(LaunchServerConfig.JarSignerConf config
|
||||||
return SignHelper.createSignedDataGenerator(c,
|
return SignHelper.createSignedDataGenerator(c,
|
||||||
config.keyAlias, config.signAlgo, config.keyPass);
|
config.keyAlias, config.signAlgo, config.keyPass);
|
||||||
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
|
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
|
||||||
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
|
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
|
||||||
logger.error("Create signedDataGenerator failed", e);
|
logger.error("Create signedDataGenerator failed", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -104,9 +104,4 @@ private void autoSign(Path inputFile, Path signedFile) throws IOException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
package pro.gravit.launchserver.binary.tasks.exe;
|
|
||||||
|
|
||||||
import net.sf.launch4j.Builder;
|
|
||||||
import net.sf.launch4j.Log;
|
|
||||||
import net.sf.launch4j.config.*;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
|
|
||||||
import pro.gravit.utils.Version;
|
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class Launch4JTask implements LauncherBuildTask, BuildExeMainTask {
|
|
||||||
public static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle
|
|
||||||
private static final String VERSION = Version.getVersion().getVersionString();
|
|
||||||
private static final int BUILD = Version.getVersion().build;
|
|
||||||
private final Path faviconFile;
|
|
||||||
private final LaunchServer server;
|
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
|
||||||
|
|
||||||
public Launch4JTask(LaunchServer launchServer) {
|
|
||||||
this.server = launchServer;
|
|
||||||
faviconFile = launchServer.dir.resolve("favicon.ico");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatVars(String mask) {
|
|
||||||
return String.format(mask, VERSION, BUILD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "launch4j";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Path process(Path inputFile) throws IOException {
|
|
||||||
logger.info("Building launcher EXE binary file (Using Launch4J)");
|
|
||||||
Path output = setConfig();
|
|
||||||
|
|
||||||
// Set favicon path
|
|
||||||
Config config = ConfigPersister.getInstance().getConfig();
|
|
||||||
if (IOHelper.isFile(faviconFile))
|
|
||||||
config.setIcon(faviconFile.toFile());
|
|
||||||
else {
|
|
||||||
config.setIcon(null);
|
|
||||||
logger.warn("Missing favicon.ico file");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start building
|
|
||||||
Builder builder = new Builder(Launch4JLog.INSTANCE);
|
|
||||||
try {
|
|
||||||
builder.build();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean allowDelete() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path setConfig() {
|
|
||||||
Path path = server.launcherEXEBinary.nextPath(getName());
|
|
||||||
Config config = new Config();
|
|
||||||
// Set file options
|
|
||||||
config.setChdir(".");
|
|
||||||
config.setErrTitle("JVM Error");
|
|
||||||
config.setDownloadUrl(server.config.launch4j.downloadUrl);
|
|
||||||
if (server.config.launch4j.supportURL != null) config.setSupportUrl(server.config.launch4j.supportURL);
|
|
||||||
// Set boolean options
|
|
||||||
config.setPriorityIndex(0);
|
|
||||||
config.setHeaderType(Config.GUI_HEADER);
|
|
||||||
config.setStayAlive(false);
|
|
||||||
config.setRestartOnCrash(false);
|
|
||||||
|
|
||||||
// Prepare JRE
|
|
||||||
Jre jre = new Jre();
|
|
||||||
jre.setMinVersion(server.config.launch4j.minVersion);
|
|
||||||
if (server.config.launch4j.setMaxVersion)
|
|
||||||
jre.setMaxVersion(server.config.launch4j.maxVersion);
|
|
||||||
jre.setRuntimeBits(Jre.RUNTIME_BITS_64_AND_32);
|
|
||||||
jre.setJdkPreference(Jre.JDK_PREFERENCE_PREFER_JRE);
|
|
||||||
config.setJre(jre);
|
|
||||||
|
|
||||||
// Prepare version info (product)
|
|
||||||
VersionInfo info = new VersionInfo();
|
|
||||||
info.setProductName(server.config.launch4j.productName);
|
|
||||||
info.setProductVersion(formatVars(server.config.launch4j.productVer));
|
|
||||||
info.setFileDescription(server.config.launch4j.fileDesc);
|
|
||||||
info.setFileVersion(formatVars(server.config.launch4j.fileVer));
|
|
||||||
info.setCopyright(server.config.launch4j.copyright);
|
|
||||||
info.setTrademarks(server.config.launch4j.trademarks);
|
|
||||||
info.setInternalName(formatVars(server.config.launch4j.internalName));
|
|
||||||
// Prepare version info (file)
|
|
||||||
info.setTxtFileVersion(formatVars(server.config.launch4j.txtFileVersion));
|
|
||||||
info.setTxtProductVersion(formatVars(server.config.launch4j.txtProductVersion));
|
|
||||||
// Prepare version info (misc)
|
|
||||||
info.setOriginalFilename(path.getFileName().toString());
|
|
||||||
info.setLanguage(LanguageID.RUSSIAN);
|
|
||||||
config.setVersionInfo(info);
|
|
||||||
|
|
||||||
// Set JAR wrapping options
|
|
||||||
config.setDontWrapJar(false);
|
|
||||||
config.setJar(server.launcherBinary.syncBinaryFile.toFile());
|
|
||||||
config.setOutfile(path.toFile());
|
|
||||||
|
|
||||||
// Return prepared config
|
|
||||||
ConfigPersister.getInstance().setAntConfig(config, null);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static class Launch4JLog extends Log {
|
|
||||||
private static final Launch4JLog INSTANCE = new Launch4JLog();
|
|
||||||
private static final Logger logger = LogManager.getLogger();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(String s) {
|
|
||||||
logger.info(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,11 @@
|
||||||
import me.tongfei.progressbar.ProgressBar;
|
import me.tongfei.progressbar.ProgressBar;
|
||||||
import me.tongfei.progressbar.ProgressBarBuilder;
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
import me.tongfei.progressbar.ProgressBarStyle;
|
import me.tongfei.progressbar.ProgressBarStyle;
|
||||||
import pro.gravit.launcher.AsyncDownloader;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.base.Downloader;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.utils.Downloader;
|
import pro.gravit.utils.command.CommandException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -29,15 +31,25 @@ public Command(Map<String, pro.gravit.utils.command.Command> childCommands, Laun
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ClientProfile.Version parseClientVersion(String arg) throws CommandException {
|
||||||
|
if(arg.isEmpty()) {
|
||||||
|
throw new CommandException("ClientVersion can't be empty");
|
||||||
|
}
|
||||||
|
return Launcher.gsonManager.gson.fromJson(arg, ClientProfile.Version.class);
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean showApplyDialog(String text) throws IOException {
|
protected boolean showApplyDialog(String text) throws IOException {
|
||||||
System.out.printf("%s [Y/N]:", text);
|
System.out.printf("%s [Y/N]:", text);
|
||||||
String response = server.commandHandler.readLine().toLowerCase(Locale.ROOT);
|
String response = server.commandHandler.readLine().toLowerCase(Locale.ROOT);
|
||||||
return response.equals("y");
|
return response.equals("y");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
|
||||||
long total = 0;
|
long total = 0;
|
||||||
for (AsyncDownloader.SizedFile file : list) {
|
for (Downloader.SizedFile file : list) {
|
||||||
|
if(file.size < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
total += file.size;
|
total += file.size;
|
||||||
}
|
}
|
||||||
long totalFiles = list.size();
|
long totalFiles = list.size();
|
||||||
|
@ -49,7 +61,7 @@ protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownload
|
||||||
.setStyle(ProgressBarStyle.COLORFUL_UNICODE_BLOCK)
|
.setStyle(ProgressBarStyle.COLORFUL_UNICODE_BLOCK)
|
||||||
.setUnit("MB", 1024 * 1024)
|
.setUnit("MB", 1024 * 1024)
|
||||||
.build();
|
.build();
|
||||||
bar.setExtraMessage(String.format(" [0/%d]", totalFiles));
|
bar.setExtraMessage(" [0/%d]".formatted(totalFiles));
|
||||||
Downloader downloader = Downloader.downloadList(list, baseUrl, targetDir, new Downloader.DownloadCallback() {
|
Downloader downloader = Downloader.downloadList(list, baseUrl, targetDir, new Downloader.DownloadCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void apply(long fullDiff) {
|
public void apply(long fullDiff) {
|
||||||
|
@ -59,7 +71,7 @@ public void apply(long fullDiff) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(Path path) {
|
public void onComplete(Path path) {
|
||||||
bar.setExtraMessage(String.format(" [%d/%d]", currentFiles.incrementAndGet(), totalFiles));
|
bar.setExtraMessage(" [%d/%d]".formatted(currentFiles.incrementAndGet(), totalFiles));
|
||||||
}
|
}
|
||||||
}, null, 4);
|
}, null, 4);
|
||||||
downloader.getFuture().handle((v, e) -> {
|
downloader.getFuture().handle((v, e) -> {
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
import pro.gravit.launchserver.command.Command;
|
import pro.gravit.launchserver.command.Command;
|
||||||
|
|
||||||
public class DebugCommand extends Command {
|
public class DebugCommand extends Command {
|
||||||
private transient Logger logger = LogManager.getLogger();
|
private final transient Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
public DebugCommand(LaunchServer server) {
|
public DebugCommand(LaunchServer server) {
|
||||||
super(server);
|
super(server);
|
||||||
}
|
}
|
||||||
|
@ -31,10 +32,11 @@ public void invoke(String... args) throws Exception {
|
||||||
boolean value = Boolean.parseBoolean(args[0]);
|
boolean value = Boolean.parseBoolean(args[0]);
|
||||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||||
Configuration config = ctx.getConfiguration();
|
Configuration config = ctx.getConfiguration();
|
||||||
|
config.getWatchManager().setIntervalSeconds(-1);
|
||||||
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
|
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
|
||||||
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
|
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
|
||||||
ctx.updateLoggers();
|
ctx.updateLoggers();
|
||||||
if(value) {
|
if (value) {
|
||||||
logger.info("Log level TRACE enabled");
|
logger.info("Log level TRACE enabled");
|
||||||
} else {
|
} else {
|
||||||
logger.info("Log level TRACE disabled");
|
logger.info("Log level TRACE disabled");
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package pro.gravit.launchserver.command.basic;
|
|
||||||
|
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
|
||||||
import pro.gravit.launchserver.command.Command;
|
|
||||||
|
|
||||||
public final class RestartCommand extends Command {
|
|
||||||
public RestartCommand(LaunchServer server) {
|
|
||||||
super(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getArgsDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsageDescription() {
|
|
||||||
return "Restart LaunchServer";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invoke(String... args) {
|
|
||||||
server.fullyRestart();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,14 +5,17 @@
|
||||||
import pro.gravit.launchserver.command.hash.*;
|
import pro.gravit.launchserver.command.hash.*;
|
||||||
import pro.gravit.launchserver.command.modules.LoadModuleCommand;
|
import pro.gravit.launchserver.command.modules.LoadModuleCommand;
|
||||||
import pro.gravit.launchserver.command.modules.ModulesCommand;
|
import pro.gravit.launchserver.command.modules.ModulesCommand;
|
||||||
|
import pro.gravit.launchserver.command.profiles.ProfilesCommand;
|
||||||
import pro.gravit.launchserver.command.service.*;
|
import pro.gravit.launchserver.command.service.*;
|
||||||
|
import pro.gravit.launchserver.command.sync.*;
|
||||||
|
import pro.gravit.launchserver.command.tools.SignDirCommand;
|
||||||
|
import pro.gravit.launchserver.command.tools.SignJarCommand;
|
||||||
import pro.gravit.utils.command.BaseCommandCategory;
|
import pro.gravit.utils.command.BaseCommandCategory;
|
||||||
import pro.gravit.utils.command.basic.ClearCommand;
|
import pro.gravit.utils.command.basic.ClearCommand;
|
||||||
import pro.gravit.utils.command.basic.GCCommand;
|
import pro.gravit.utils.command.basic.GCCommand;
|
||||||
import pro.gravit.utils.command.basic.HelpCommand;
|
import pro.gravit.utils.command.basic.HelpCommand;
|
||||||
|
|
||||||
public abstract class CommandHandler extends pro.gravit.utils.command.CommandHandler {
|
public abstract class CommandHandler extends pro.gravit.utils.command.CommandHandler {
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public static void registerCommands(pro.gravit.utils.command.CommandHandler handler, LaunchServer server) {
|
public static void registerCommands(pro.gravit.utils.command.CommandHandler handler, LaunchServer server) {
|
||||||
BaseCommandCategory basic = new BaseCommandCategory();
|
BaseCommandCategory basic = new BaseCommandCategory();
|
||||||
// Register basic commands
|
// Register basic commands
|
||||||
|
@ -20,7 +23,6 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
||||||
basic.registerCommand("version", new VersionCommand(server));
|
basic.registerCommand("version", new VersionCommand(server));
|
||||||
basic.registerCommand("build", new BuildCommand(server));
|
basic.registerCommand("build", new BuildCommand(server));
|
||||||
basic.registerCommand("stop", new StopCommand(server));
|
basic.registerCommand("stop", new StopCommand(server));
|
||||||
basic.registerCommand("restart", new RestartCommand(server));
|
|
||||||
basic.registerCommand("debug", new DebugCommand(server));
|
basic.registerCommand("debug", new DebugCommand(server));
|
||||||
basic.registerCommand("clear", new ClearCommand(handler));
|
basic.registerCommand("clear", new ClearCommand(handler));
|
||||||
basic.registerCommand("gc", new GCCommand());
|
basic.registerCommand("gc", new GCCommand());
|
||||||
|
@ -35,12 +37,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
||||||
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
|
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
|
||||||
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
|
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
|
||||||
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
|
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
|
||||||
updates.registerCommand("syncBinaries", new SyncBinariesCommand(server));
|
updates.registerCommand("sync", new SyncCommand(server));
|
||||||
updates.registerCommand("syncUpdates", new SyncUpdatesCommand(server));
|
updates.registerCommand("profile", new ProfilesCommand(server));
|
||||||
updates.registerCommand("syncProfiles", new SyncProfilesCommand(server));
|
|
||||||
updates.registerCommand("syncUP", new SyncUPCommand(server));
|
|
||||||
updates.registerCommand("saveProfiles", new SaveProfilesCommand(server));
|
|
||||||
updates.registerCommand("makeProfile", new MakeProfileCommand(server));
|
|
||||||
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
|
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
|
||||||
handler.registerCategory(updatesCategory);
|
handler.registerCategory(updatesCategory);
|
||||||
|
|
||||||
|
@ -51,11 +49,16 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
|
||||||
service.registerCommand("notify", new NotifyCommand(server));
|
service.registerCommand("notify", new NotifyCommand(server));
|
||||||
service.registerCommand("component", new ComponentCommand(server));
|
service.registerCommand("component", new ComponentCommand(server));
|
||||||
service.registerCommand("clients", new ClientsCommand(server));
|
service.registerCommand("clients", new ClientsCommand(server));
|
||||||
service.registerCommand("signJar", new SignJarCommand(server));
|
|
||||||
service.registerCommand("signDir", new SignDirCommand(server));
|
|
||||||
service.registerCommand("securitycheck", new SecurityCheckCommand(server));
|
service.registerCommand("securitycheck", new SecurityCheckCommand(server));
|
||||||
service.registerCommand("token", new TokenCommand(server));
|
service.registerCommand("token", new TokenCommand(server));
|
||||||
Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components");
|
Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components");
|
||||||
handler.registerCategory(serviceCategory);
|
handler.registerCategory(serviceCategory);
|
||||||
|
|
||||||
|
//Register tools commands
|
||||||
|
BaseCommandCategory tools = new BaseCommandCategory();
|
||||||
|
tools.registerCommand("signJar", new SignJarCommand(server));
|
||||||
|
tools.registerCommand("signDir", new SignDirCommand(server));
|
||||||
|
Category toolsCategory = new Category(tools, "tools", "Other tools");
|
||||||
|
handler.registerCategory(toolsCategory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
package pro.gravit.launchserver.command.hash;
|
package pro.gravit.launchserver.command.hash;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import pro.gravit.launcher.base.Launcher;
|
||||||
|
import pro.gravit.launcher.base.Downloader;
|
||||||
|
import pro.gravit.launchserver.HttpRequester;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.command.Command;
|
import pro.gravit.launchserver.command.Command;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
|
import java.io.Writer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class DownloadAssetCommand extends Command {
|
public final class DownloadAssetCommand extends Command {
|
||||||
|
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||||
|
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
private transient final Logger logger = LogManager.getLogger();
|
||||||
|
|
||||||
public DownloadAssetCommand(LaunchServer server) {
|
public DownloadAssetCommand(LaunchServer server) {
|
||||||
|
@ -19,7 +29,7 @@ public DownloadAssetCommand(LaunchServer server) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getArgsDescription() {
|
public String getArgsDescription() {
|
||||||
return "[version] [dir]";
|
return "[version] [dir] (mojang/mirror)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,23 +39,97 @@ public String getUsageDescription() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(String... args) throws Exception {
|
public void invoke(String... args) throws Exception {
|
||||||
verifyArgs(args, 2);
|
verifyArgs(args, 1);
|
||||||
//Version version = Version.byName(args[0]);
|
//Version version = Version.byName(args[0]);
|
||||||
String versionName = args[0];
|
String versionName = args[0];
|
||||||
String dirName = IOHelper.verifyFileName(args[1]);
|
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets");
|
||||||
|
String type = args.length > 2 ? args[2] : "mojang";
|
||||||
Path assetDir = server.updatesDir.resolve(dirName);
|
Path assetDir = server.updatesDir.resolve(dirName);
|
||||||
|
|
||||||
// Create asset dir
|
// Create asset dir
|
||||||
logger.info("Creating asset dir: '{}'", dirName);
|
if (Files.notExists(assetDir)) {
|
||||||
Files.createDirectory(assetDir);
|
logger.info("Creating asset dir: '{}'", dirName);
|
||||||
|
Files.createDirectory(assetDir);
|
||||||
|
}
|
||||||
|
|
||||||
// Download required asset
|
if (type.equals("mojang")) {
|
||||||
logger.info("Downloading asset, it may take some time");
|
HttpRequester requester = new HttpRequester();
|
||||||
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
|
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL);
|
||||||
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
|
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
|
||||||
|
String profileUrl = null;
|
||||||
|
for (var e : versions.versions) {
|
||||||
|
if (e.id.equals(versionName)) {
|
||||||
|
profileUrl = e.url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (profileUrl == null) {
|
||||||
|
logger.error("Version {} not found", versionName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info("Fetch profile {} from {}", versionName, profileUrl);
|
||||||
|
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
|
||||||
|
String assetsIndexUrl = profileInfo.assetIndex.url;
|
||||||
|
String assetIndex = profileInfo.assetIndex.id;
|
||||||
|
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json");
|
||||||
|
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
|
||||||
|
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
|
||||||
|
JsonObject objects = assets.get("objects").getAsJsonObject();
|
||||||
|
try (Writer writer = IOHelper.newWriter(indexPath)) {
|
||||||
|
logger.info("Save {}", indexPath);
|
||||||
|
Launcher.gsonManager.configGson.toJson(assets, writer);
|
||||||
|
}
|
||||||
|
if (!assetIndex.equals(versionName)) {
|
||||||
|
Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json");
|
||||||
|
logger.info("Copy {} into {}", indexPath, targetPath);
|
||||||
|
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
|
||||||
|
for (var e : objects.entrySet()) {
|
||||||
|
var value = e.getValue().getAsJsonObject();
|
||||||
|
var hash = value.get("hash").getAsString();
|
||||||
|
hash = hash.substring(0, 2) + "/" + hash;
|
||||||
|
var size = value.get("size").getAsLong();
|
||||||
|
var path = "objects/" + hash;
|
||||||
|
var target = assetDir.resolve(path);
|
||||||
|
if (Files.exists(target)) {
|
||||||
|
long fileSize = Files.size(target);
|
||||||
|
if (fileSize != size) {
|
||||||
|
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toDownload.add(new Downloader.SizedFile(hash, path, size));
|
||||||
|
}
|
||||||
|
logger.info("Download {} files", toDownload.size());
|
||||||
|
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
|
||||||
|
downloader.getFuture().get();
|
||||||
|
} else {
|
||||||
|
// Download required asset
|
||||||
|
logger.info("Downloading asset, it may take some time");
|
||||||
|
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
|
||||||
|
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
|
||||||
|
}
|
||||||
|
|
||||||
// Finished
|
// Finished
|
||||||
server.syncUpdatesDir(Collections.singleton(dirName));
|
server.syncUpdatesDir(Collections.singleton(dirName));
|
||||||
logger.info("Asset successfully downloaded: '{}'", dirName);
|
logger.info("Asset successfully downloaded: '{}'", dirName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record MiniVersionInfo(String id, String url) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MinecraftVersions(List<MiniVersionInfo> versions) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MinecraftAssetIndexInfo(String id, String url) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,17 @@
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launcher.profiles.ClientProfile;
|
import pro.gravit.launcher.base.profiles.ClientProfile;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
|
||||||
|
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.command.Command;
|
import pro.gravit.launchserver.command.Command;
|
||||||
import pro.gravit.launchserver.helper.MakeProfileHelper;
|
import pro.gravit.launchserver.helper.MakeProfileHelper;
|
||||||
import pro.gravit.utils.command.CommandException;
|
import pro.gravit.utils.command.CommandException;
|
||||||
import pro.gravit.utils.helper.IOHelper;
|
import pro.gravit.utils.helper.IOHelper;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -41,18 +41,14 @@ public void invoke(String... args) throws IOException, CommandException {
|
||||||
verifyArgs(args, 2);
|
verifyArgs(args, 2);
|
||||||
//Version version = Version.byName(args[0]);
|
//Version version = Version.byName(args[0]);
|
||||||
String versionName = args[0];
|
String versionName = args[0];
|
||||||
String dirName = IOHelper.verifyFileName(args[1]);
|
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
|
||||||
Path clientDir = server.updatesDir.resolve(args[1]);
|
Path clientDir = server.updatesDir.resolve(dirName);
|
||||||
|
|
||||||
boolean isMirrorClientDownload = false;
|
boolean isMirrorClientDownload = false;
|
||||||
if (args.length > 2) {
|
if (args.length > 2) {
|
||||||
isMirrorClientDownload = args[2].equals("mirror");
|
isMirrorClientDownload = args[2].equals("mirror");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client dir
|
|
||||||
logger.info("Creating client dir: '{}'", dirName);
|
|
||||||
Files.createDirectory(clientDir);
|
|
||||||
|
|
||||||
// Download required client
|
// Download required client
|
||||||
logger.info("Downloading client, it may take some time");
|
logger.info("Downloading client, it may take some time");
|
||||||
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
|
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
|
||||||
|
@ -60,43 +56,47 @@ public void invoke(String... args) throws IOException, CommandException {
|
||||||
|
|
||||||
// Create profile file
|
// Create profile file
|
||||||
logger.info("Creaing profile file: '{}'", dirName);
|
logger.info("Creaing profile file: '{}'", dirName);
|
||||||
ClientProfile client = null;
|
ClientProfile clientProfile = null;
|
||||||
|
if (isMirrorClientDownload) {
|
||||||
|
try {
|
||||||
|
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
|
||||||
|
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
|
||||||
|
var builder = new ClientProfileBuilder(clientProfile);
|
||||||
|
builder.setTitle(dirName);
|
||||||
|
builder.setDir(dirName);
|
||||||
|
builder.setUuid(UUID.randomUUID());
|
||||||
|
clientProfile = builder.createClientProfile();
|
||||||
|
if (clientProfile.getServers() != null) {
|
||||||
|
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
|
||||||
|
if (serverProfile != null) {
|
||||||
|
serverProfile.name = dirName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
|
||||||
|
isMirrorClientDownload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!isMirrorClientDownload) {
|
if (!isMirrorClientDownload) {
|
||||||
try {
|
try {
|
||||||
String internalVersion = versionName;
|
String internalVersion = versionName;
|
||||||
if (internalVersion.contains("-")) {
|
if (internalVersion.contains("-")) {
|
||||||
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
|
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
|
||||||
}
|
}
|
||||||
ClientProfile.Version version = ClientProfile.Version.byName(internalVersion);
|
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
|
||||||
if (version.compareTo(ClientProfile.Version.MC164) <= 0) {
|
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
|
||||||
logger.warn("Minecraft 1.6.4 and below not supported. Use at your own risk");
|
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
|
||||||
}
|
}
|
||||||
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
|
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
|
||||||
for (MakeProfileHelper.MakeProfileOption option : options) {
|
for (MakeProfileHelper.MakeProfileOption option : options) {
|
||||||
logger.debug("Detected option {}", option.getClass().getSimpleName());
|
logger.debug("Detected option {}", option.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
client = MakeProfileHelper.makeProfile(version, dirName, options);
|
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
isMirrorClientDownload = true;
|
isMirrorClientDownload = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMirrorClientDownload) {
|
server.config.profileProvider.addProfile(clientProfile);
|
||||||
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
|
|
||||||
client = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
|
|
||||||
client.setTitle(dirName);
|
|
||||||
client.setDir(dirName);
|
|
||||||
client.setUUID(UUID.randomUUID());
|
|
||||||
if (client.getServers() != null) {
|
|
||||||
ClientProfile.ServerProfile serverProfile = client.getDefaultServerProfile();
|
|
||||||
if (serverProfile != null) {
|
|
||||||
serverProfile.name = dirName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
|
|
||||||
dirName, "json"))) {
|
|
||||||
Launcher.gsonManager.configGson.toJson(client, writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finished
|
// Finished
|
||||||
server.syncProfilesDir();
|
server.syncProfilesDir();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import pro.gravit.launcher.Launcher;
|
import pro.gravit.launcher.base.Launcher;
|
||||||
import pro.gravit.launchserver.LaunchServer;
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.command.Command;
|
import pro.gravit.launchserver.command.Command;
|
||||||
import pro.gravit.utils.command.CommandException;
|
import pro.gravit.utils.command.CommandException;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue