Registration

Needed data

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).

Getting the Firebase token

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.

Generating the certificate

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.

Getting the endpoint

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.

/register/new

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"
}

/register/hebe

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"
}

What’s next?

Next requests should be sent to {unit-endpoint}, which is body.Envelope[*].Unit.RestURL + /mobile.