To register a device to an UONET+ API account, you need this set of data:
token (provided by UONET+), e.g. OP1JW6K
, later: {token}
PIN (provided by UONET+), e.g. 493425
, later: {pin}
symbol of school’s administrative unit (borough [gmina] or county [powiat], depending on school type), e.g. opole
, powiatnyski
(provided by UONET+; also see: symbol generator by Wulkanowy project), later: {symbol}
Firebase (Google) Cloud Messaging token, later: {firebase-token}
device name (could be any).
There are 2 ways to get the Firebase token:
Simulate Google app’s requests manually, as in erupcja/fcm-listener-node, to get the token directly from Google. Requires app’s Firebase keys and other data (could be extracted from APK with erupcja/apk-firebase-data-extractor). No way a code like this will pass in a Google Play app.
Hijack the token from the official app. The app can be manipulated to connect with your HTTP(S) server by either a QR code you can generate with wulkanowy/qr or by HTTP (no HTTPS!) MITM. Reference hijack implementation: erupcja/vulcan-firebase-token-hijacker.
The app has to generate a X.509 RSA or ECDSA certificate, a different one for each user, to sign the API requests.
{cert-fingerprint}
- SHA-1 hash of DER form of certificate
{cert-pem}
- PEM form of the certificate without the headers (-----BEGIN CERTIFICATE-----
, -----END CERTIFICATE-----
)
{cert-private-key}
- PEM form of the PKCS#8 certificate’s private key without the headers (-----BEGIN PRIVATE KEY-----
, -----END PRIVATE KEY-----
)
See the ready-to-use implementations: erupcja/uonetplus-hebe-certificate-generator.
There are 2 ways for that:
decoding and parsing QR code
from a Vulcan’s text file
QR code content are encrypted by aes-128-ecb
algorithm with password tDVS4ykCBBAeN33h
.
See github.com/wulkanowy/qr for libraries.
Example decoded content: CERT#https://uonetplus-komunikacja.eszkola.opolskie.pl/opole/mobile-api#OP1JW6K#ENDCERT
Parsing regex (ECMAScript notation): /^CERT#([^#]+)#([A-Z0-9]{7})#ENDCERT$/
mobile-api
should be replaced with api/mobile
by the app (for the sake of compatibility with pl.vulcan.uonetmobile
).
This means that example {raw-endpoint}
is https://uonetplus-komunikacja.eszkola.opolskie.pl/opole/api/mobile
.
The no-QR method is to get the matching endpoint from file. Make a simple HTTP GET request to https://komponenty.vulcan.net.pl/UonetPlusMobile/RoutingRules.txt
to obtain it. App should do this dynamically, as new UONET+ instances can be created by the time.
Line OP1,https://uonetplus-komunikacja.eszkola.opolskie.pl
means that tokens starting with OP1
should use https://uonetplus-komunikacja.eszkola.opolskie.pl
as the correct server.
Now the {raw-endpoint}
is {server}/{symbol}/api/mobile
, for example: https://uonetplus-komunikacja.eszkola.opolskie.pl/opole/api/mobile
.
Request:
POST {raw-endpoint}/register/new HTTP/1.1
vOS: Android
vDeviceModel: Redmi Note 3
vAPI: 1
vDate: Wed, 08 Apr 2020 20:49:11 GMT
vCanonicalUrl: api%2fmobile%2fregister%2fnew
Digest: SHA-256=EElE76CuYuIE08hH03kdewLfYr6x9YU8E8AOBue3yK4=
Signature: keyId="{cert-fingerprint}",headers="vCanonicalUrl Digest vDate",algorithm="sha256withecdsa",signature=Base64(SHA256withECDSA(MEUCIG1VRVdOqPcjWcvRN5CtdrSjnoqAZqgiexTVWc5zA+9TAiEAneXzS4pk7HGy/u2IlvMRApKu6lOC1/6XUBv+GbKKO0U=))
Content-Type: application/json; charset=utf-8
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.11.0
{
"API": 1,
"AppName": "DzienniczekPlus 2.0",
"AppVersion": "1.0",
"CertificateId": "{cert-fingerprint}",
"Envelope": {
"Certificate": "{cert-pem}\n",
"CertificateThumbprint": "{cert-fingerprint}",
"CertificateType": "X509",
"DeviceModel": "Xiaomi Redmi Note 3",
"OS": "Android",
"PIN": "{pin}",
"SecurityToken": "{token}",
"SelfIdentifier": "5a09095f-11a5-4582-9906-a871c5e0814f"
},
"FirebaseToken": "{firebase-token}",
"RequestId": "f94790a5-9c7e-4956-b913-430a670ce8a7",
"Timestamp": 1586378950907,
"TimestampFormatted": "2020-04-08 22:49:11"
}
Response:
HTTP/1.1 200 OK
Content-Length: 359
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
Set-Cookie: ARR_uonetplus-komunikacja.eszkola.opolskie.pl=8a141ee1938645c8e894da9b020145905e68ccfdfd56e212f2bb21bfd8f761de;Path=/;Domain=uonetplus-komunikacja.eszkola.opolskie.pl
X-AspNet-Version: 4.0.30319
X-Powered-By: ARR/2.5
X-Powered-By: ASP.NET
Date: Wed, 08 Apr 2020 20:49:13 GMT
{
"Envelope": {
"LoginId": 177013,
"RestURL": "https://uonetplus-komunikacja.eszkola.opolskie.pl/opole/",
"UserLogin": "laura@erupcja.science",
"UserName": "laura@erupcja.science"
},
"EnvelopeType": "AccountPayload",
"InResponseTo": null,
"RequestId": "b26023b0-d547-495c-b79e-5336900ef107",
"Status": {
"Code": 0,
"Message": "OK"
},
"Timestamp": 1586378954618,
"TimestampFormatted": "2020-04-08 22:49:14"
}
Request:
GET {raw-endpoint}/register/hebe HTTP/1.1
vOS: Android
vDeviceModel: Redmi Note 3
vAPI: 1
vDate: Wed, 08 Apr 2020 20:49:14 GMT
vCanonicalUrl: api%2fmobile%2fregister%2fhebe
Signature: keyId="{cert-fingerprint}",headers="vCanonicalUrl vDate",algorithm="sha256withecdsa",signature=Base64(SHA256withECDSA(MEUCIQDDqLOuUrlzi7VWTO0gOM8Cr3wKcTzirMV9xQJqcwFkQAIgdlizXw53XCdIiX8X9xO3mKScoXj4+lFz1a8XJg8phy8=))
Content-Type: application/json; charset=utf-8
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.11.0
Response:
{
"Envelope": [
{
"Capabilities": [
"REGULAR",
"TOPICS_ENABLED",
"ADDRESS_BOOK_PUPIL"
],
"ClassDisplay": "2et",
"ConstituentUnit": {
"Address": "ul.Tadeusza Kościuszki 39-41, 45-062 Opole",
"Id": 3,
"Name": "Publiczne Technikum Nr 5 w Zespole Szkół Elektrycznych im. Tadeusza Kościuszki w Opolu",
"Patron": null,
"SchoolTopic": "bab6ff93-23b6-e411-9b68-005056b435d0",
"Short": "PT5"
},
"Educators": [
{
"Id": "e-234",
"Initials": "MK",
"LoginId": 4727,
"Name": "Kurisu",
"Roles": [
{
"Address": "Makise Kurisu [MK] - wychowawca 2et (PT5)",
"AddressHash": "7042b84fe7da034d62251907fdae6c2d2e3373f0",
"ClassSymbol": "2et (PT5)",
"ConstituentUnitSymbol": "PT5",
"Initials": "MK",
"Name": "Kurisu",
"RoleName": "Wychowawca",
"RoleOrder": 0,
"Surname": "Makise",
"UnitSymbol": null
}
],
"Surname": "Makise"
}
],
"FullSync": false,
"InfoDisplay": "OpoleZSE - et2",
"Journal": {
"Id": 796,
"YearEnd": {
"Date": "2020-08-31",
"DateDisplay": "31.08.2020",
"Time": "00:00:00",
"Timestamp": 1598824800000
},
"YearStart": {
"Date": "2019-09-01",
"DateDisplay": "01.09.2019",
"Time": "00:00:00",
"Timestamp": 1567288800000
}
},
"Login": {
"DisplayName": "Laura Karaś",
"FirstName": "Laura",
"Id": 177013,
"LoginRole": "Uczen",
"SecondName": "Lena",
"Surname": "Karaś",
"Value": "laura@erupcja.science"
},
"Partition": "opole-OpoleZSE",
"Periods": [
{
"Current": false,
"End": {
"Date": "2019-01-01",
"DateDisplay": "01.01.2019",
"Time": "00:00:00",
"Timestamp": 1546297200000
},
"Id": 1725,
"Last": false,
"Level": 1,
"Number": 1,
"Start": {
"Date": "2018-09-01",
"DateDisplay": "01.09.2018",
"Time": "00:00:00",
"Timestamp": 1535752800000
}
},
{
"Current": false,
"End": {
"Date": "2019-08-31",
"DateDisplay": "31.08.2019",
"Time": "00:00:00",
"Timestamp": 1567202400000
},
"Id": 1726,
"Last": true,
"Level": 1,
"Number": 2,
"Start": {
"Date": "2019-01-02",
"DateDisplay": "02.01.2019",
"Time": "00:00:00",
"Timestamp": 1546383600000
}
},
{
"Current": false,
"End": {
"Date": "2020-01-01",
"DateDisplay": "01.01.2020",
"Time": "00:00:00",
"Timestamp": 1577833200000
},
"Id": 1727,
"Last": false,
"Level": 2,
"Number": 1,
"Start": {
"Date": "2019-09-01",
"DateDisplay": "01.09.2019",
"Time": "00:00:00",
"Timestamp": 1567288800000
}
},
{
"Current": true,
"End": {
"Date": "2020-08-31",
"DateDisplay": "31.08.2020",
"Time": "00:00:00",
"Timestamp": 1598824800000
},
"Id": 1728,
"Last": true,
"Level": 2,
"Number": 2,
"Start": {
"Date": "2020-01-02",
"DateDisplay": "02.01.2020",
"Time": "00:00:00",
"Timestamp": 1577919600000
}
}
],
"Pupil": {
"FirstName": "Laura",
"Id": 2137,
"LoginId": 177013,
"LoginValue": "laura@erupcja.science",
"SecondName": "Lena",
"Sex": false,
"Surname": "Karaś"
},
"SenderEntry": {
"Address": "Karaś Laura - uczennica 2et (PT5)",
"AddressHash": "5dfca102c59680cf9a16a629157cee630f56304f",
"Initials": "KL",
"LoginId": 177013
},
"TopLevelPartition": "opole",
"Unit": {
"Address": "ul.Tadeusza Kościuszki 39-41, 45-062 Opole",
"DisplayName": "Zespół Szkół Elektrycznych im.Tadeusza Kościuszki",
"Id": 3,
"Name": "Zespół Szkół Elektrycznych ",
"Patron": "Tadeusz Kościuszko",
"RestURL": "https://uonetplus-komunikacja.eszkola.opolskie.pl/opole/OpoleZSE/api",
"Short": "OpoleZSE",
"Symbol": "OpoleZSE"
}
}
],
"EnvelopeType": "IEnumerable`1",
"InResponseTo": null,
"RequestId": "b860931a-ae3e-4939-a27f-adcfbbf95143",
"Status": {
"Code": 0,
"Message": "OK"
},
"Timestamp": 1586378956901,
"TimestampFormatted": "2020-04-08 22:49:16"
}
Next requests should be sent to {unit-endpoint}
, which is body.Envelope[*].Unit.RestURL
+ /mobile
.