| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License |
| |
| #include <gtest/gtest.h> |
| |
| #include <string> |
| |
| #include <process/jwt.hpp> |
| |
| #include <process/ssl/utilities.hpp> |
| |
| #include <stout/base64.hpp> |
| #include <stout/gtest.hpp> |
| #include <stout/stringify.hpp> |
| #include <stout/strings.hpp> |
| |
| using process::http::authentication::JWT; |
| using process::http::authentication::JWTError; |
| |
| using process::network::openssl::generate_hmac_sha256; |
| using process::network::openssl::pem_to_rsa_private_key; |
| using process::network::openssl::pem_to_rsa_public_key; |
| using process::network::openssl::sign_rsa_sha256; |
| |
| using std::shared_ptr; |
| using std::string; |
| |
| // Private and public keys generated by openssl. |
| |
| static const char PRIVATE_KEY[] = |
| "-----BEGIN PRIVATE KEY-----\n" |
| "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7Fzlng3c3SGcH\n" |
| "W0Icz5na8hb1dRg9G5jdi2+sfcssn05zbTl54y7Jl1SxEYqyu6RMvU/eWNeYowtO\n" |
| "lgeyTHGdy9kCLYTwapRh4wCSQdpY5cLwWhoVV5VzR4v/fTfViNFFOZArKUy3juBg\n" |
| "0iHcGETS4/8mpWKRJbfuzrjo+zxqFtGlFvj0knALVW9DKAd4DsgdAfelyAng5nC/\n" |
| "w1rKzCoEfPDTkZp3CA5dr0chImfHDc0jIn8xQpfzOZiH8oxobbAEPZ/POcvaodwQ\n" |
| "9aE1Pwp2cBGZ9PewpDbg+K9T+mtEnaQmJfCOqalKme0nIOy8peJWAT4hyhrChj6q\n" |
| "Ls/+hw5nAgMBAAECggEAXatnBjh/+6G4U6qREzOtaD1E2Wfi1tV2V5V0N/xTOOgZ\n" |
| "sxjAahIgXrXxpSWPN6VSwUkXL89zQex/wLzE5bP4PnTNFZYMtQHngIrSwmdOFqwS\n" |
| "SZwS6xSKssjjgusChVqWy/3h/HoU+uIB3PfYFAXij2OvX332N42W5W2CjsMaoFIp\n" |
| "Fe0i/PkPDoSCYok/DOzMTbXUITUb3fp/Fnlk3onk2+AOVw/jxL/u5teYEFMH8NX9\n" |
| "l3t0Bon3X98mq179nJGgKRSrRglhbHkObqudC2Trz+HoRrvNjQjYEKvloQjd4Fgy\n" |
| "3xV3nFAUI9WgT6OHp5uHDPoDybqHDSp/gn/p+T8JoQKBgQDkQm+0DAglSlMLazeO\n" |
| "NxYcg6TLRwZkJAfJwoBzd5/l4UhwAhLKlpGrCPeiTVtVS1+8/sQ4pjy94vQyzWDz\n" |
| "7YA/9nzsVxj4+U1K/jD3vhZWj9xXbpedW1093zEavlV/F0/enkcgsIwnQ6mWg9sY\n" |
| "ziKyOT/yAqFRWvDjmPcB/miQswKBgQDR0/PgvsCQCgonXqLovk3j2QMHrUwr+I1o\n" |
| "yLQBwjiRHTS8Ts9GjmMyn+jOHb90kjpzB24iE5LtvdSvZDP2s2uLPv4dpjesGvPM\n" |
| "huCoaYF2+emkUZgNlfvwmaN0ZtgEymuc10rVWKwAI8Ll93kzr1KLhFOQUDtJfCsu\n" |
| "NqQX1Gt9fQKBgC6GVBpQsYBYS+Dx85rrI3igZICCc40JwwSevmvKoC7M4mTiJ05f\n" |
| "rkU8SK0uM0WJXXQ6QWiCibLyhW+taOuPJyriZMgPYKmuttBoSzbT6d2u6OxxQDn0\n" |
| "m2a3DV00Gl0TNVZc0IabNZXzNqfVLF079tp4zM3ZN2RLsvnQ/dfMMSf9AoGADVAD\n" |
| "QxkXIoxghIrujxGz42bbWFtYX9nPLvy83vexmxNdSy083V8fUBDxNlKQ2RaF+tJX\n" |
| "3HWddtP6cH5NBbPweM8wVDU9hv/Ww/0yt7yp6CCHAFPk78e6SlOVGUeFIRiupy7J\n" |
| "oquTjha2wNxopizTceKdYqSUfl8QZkg1NQXXJAkCgYAbObXvDbWEpvem/9YQXW8C\n" |
| "Onts+PKx5dqpUx5vWiJyIIh04cXlHQIeu/P673NPTiORMRO/IFulk6jjS5XE8Aah\n" |
| "p4D755HSFUgQHG+5AF5SMq4cI4Agt97awyH7RHYVoSealSN0yKV4ON7Cs5mRX4Ze\n" |
| "f8lvmXYQjTzoJ1pPjwJE1Q==\n" |
| "-----END PRIVATE KEY-----\n"; |
| |
| |
| static const char PUBLIC_KEY[] = |
| "-----BEGIN PUBLIC KEY-----\n" |
| "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuxc5Z4N3N0hnB1tCHM+Z\n" |
| "2vIW9XUYPRuY3YtvrH3LLJ9Oc205eeMuyZdUsRGKsrukTL1P3ljXmKMLTpYHskxx\n" |
| "ncvZAi2E8GqUYeMAkkHaWOXC8FoaFVeVc0eL/3031YjRRTmQKylMt47gYNIh3BhE\n" |
| "0uP/JqVikSW37s646Ps8ahbRpRb49JJwC1VvQygHeA7IHQH3pcgJ4OZwv8Nayswq\n" |
| "BHzw05GadwgOXa9HISJnxw3NIyJ/MUKX8zmYh/KMaG2wBD2fzznL2qHcEPWhNT8K\n" |
| "dnARmfT3sKQ24PivU/prRJ2kJiXwjqmpSpntJyDsvKXiVgE+IcoawoY+qi7P/ocO\n" |
| "ZwIDAQAB\n" |
| "-----END PUBLIC KEY-----\n"; |
| |
| |
| // This public key is not linked to the private key above. |
| static const char WRONG_PUBLIC_KEY[] = |
| "-----BEGIN PUBLIC KEY-----\n" |
| "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuFD5lVAz2AjFEjzIWMwB\n" |
| "jtLrQNxaXy2wk9Yiwg8UOeYJevkG3/+UWXAEszurqJ7lq/4uSPAbTECbtyiE4dTv\n" |
| "ESFmqHqfwKYk+VFvP8Ty4Vh9brt4/RiZ1xL10MFif/vu38mLsvRPbDraTusT+9ni\n" |
| "TQyhJCdiRGrq5hmiW3BoA7/elpvp0+i/0e1FxMREcNOUqQ1HhYMFn3MskqjZRs2g\n" |
| "igmknQPVPoJBdBWQVGIYVb52d34hmLhvYPD3f4hJHS+mZvS5W6jekCXK4HAheDNx\n" |
| "OvBE4V1dtILdJPJEsx3Ua91z8IEaYa8iwbOV3yejdZVZ3NHSxmtrARYBpo5K9Bw4\n" |
| "jQIDAQAB\n" |
| "-----END PUBLIC KEY-----"; |
| |
| |
| TEST(JWTTest, Parse) |
| { |
| const string secret = "secret"; |
| |
| shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get(); |
| CHECK_NOTNULL(privateKey.get()); |
| |
| shared_ptr<RSA> publicKey = pem_to_rsa_public_key(PUBLIC_KEY).get(); |
| CHECK_NOTNULL(publicKey.get()); |
| |
| shared_ptr<RSA> wrongPublicKey = |
| pem_to_rsa_public_key(WRONG_PUBLIC_KEY).get(); |
| CHECK_NOTNULL(wrongPublicKey.get()); |
| |
| auto create_hs256_token = [secret](string header, string payload) { |
| header = base64::encode_url_safe(header, false); |
| payload = base64::encode_url_safe(payload, false); |
| |
| const string mac = |
| generate_hmac_sha256(strings::join(".", header, payload), secret).get(); |
| |
| const string signature = base64::encode_url_safe(mac, false); |
| |
| return strings::join(".", header, payload, signature); |
| }; |
| |
| auto create_rs256_token = [privateKey](string header, string payload) { |
| header = base64::encode_url_safe(header, false); |
| payload = base64::encode_url_safe(payload, false); |
| |
| const string rawSignature = sign_rsa_sha256( |
| strings::join(".", header, payload), privateKey).get(); |
| |
| const string signature = base64::encode_url_safe(rawSignature, false); |
| |
| return strings::join(".", header, payload, signature); |
| }; |
| |
| // Invalid token header. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "NOT A VALID HEADER", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "NOT A VALID HEADER", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Invalid token payload. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"typ\":\"JWT\"}", |
| "INVALID PAYLOAD"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "INVALID PAYLOAD"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Unsupported token alg. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"RS512\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS512\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Unknown token alg. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"NOT A VALID ALG\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"NOT A VALID ALG\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // 'crit' in header. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"crit\":[\"exp\"],\"exp\":99,\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"crit\":[\"exp\"],\"exp\":99,\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Missing signature. |
| { |
| // HS256. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) + |
| "."; |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) + |
| "."; |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Wrong signature. |
| { |
| // HS256. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) + |
| "." + |
| "rm4sQe5q4sdHIJgt7mjKsnuZeP4eRquoZuncSsscqbQ"; |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) + |
| "." + |
| "rm4sQe5q4sdHIJgt7mjKsnuZeP4eRquoZuncSsscqbQ"; |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // 'none' alg with signature. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"none\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // 'none' alg missing dot. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"none\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // Expiration date is not a number. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":\"NOT A NUMBER\",\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":\"NOT A NUMBER\",\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Expiration date expired. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":0,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":0,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| } |
| |
| // Expiration date not set. |
| { |
| // HS256. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"typ\":\"JWT\"}", |
| "{\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_SOME(jwt); |
| } |
| |
| // RS256. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "{\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_SOME(jwt); |
| } |
| } |
| |
| // Wrong public key when verifying RS256 token. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, wrongPublicKey); |
| |
| EXPECT_ERROR(jwt); |
| } |
| |
| // Valid unsecure token. |
| { |
| const string token = |
| base64::encode_url_safe("{\"alg\":\"none\",\"typ\":\"JWT\"}", false) + |
| "." + |
| base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) + |
| "."; |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token); |
| |
| EXPECT_SOME(jwt); |
| } |
| |
| // Valid HS256 token. |
| { |
| const string token = create_hs256_token( |
| "{\"alg\":\"HS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, secret); |
| |
| EXPECT_SOME(jwt); |
| } |
| |
| // Valid RS256 token. |
| { |
| const string token = create_rs256_token( |
| "{\"alg\":\"RS256\",\"typ\":\"JWT\"}", |
| "{\"exp\":9999999999,\"sub\":\"foo\"}"); |
| |
| const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey); |
| |
| EXPECT_SOME(jwt); |
| } |
| } |
| |
| |
| TEST(JWTTest, Create) |
| { |
| const string secret = "secret"; |
| shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get(); |
| CHECK_NOTNULL(privateKey.get()); |
| |
| auto create_hs256_signature = [secret](const JSON::Object& payload) { |
| const string message = strings::join( |
| ".", |
| base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false), |
| base64::encode_url_safe(stringify(payload), false)); |
| |
| const string mac = generate_hmac_sha256(message, secret).get(); |
| |
| return base64::encode_url_safe(mac, false); |
| }; |
| |
| auto create_rs256_signature = [privateKey](const JSON::Object& payload) { |
| const string message = strings::join( |
| ".", |
| base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false), |
| base64::encode_url_safe(stringify(payload), false)); |
| |
| const string mac = sign_rsa_sha256(message, privateKey).get(); |
| |
| return base64::encode_url_safe(mac, false); |
| }; |
| |
| JSON::Object payload; |
| payload.values["exp"] = 9999999999; |
| payload.values["sub"] = "foo"; |
| |
| // HS256 signed JWT. |
| { |
| const Try<JWT, JWTError> jwt = JWT::create(payload, secret); |
| |
| EXPECT_SOME(jwt); |
| |
| EXPECT_EQ(JWT::Alg::HS256, jwt->header.alg); |
| EXPECT_SOME_EQ("JWT", jwt->header.typ); |
| |
| EXPECT_EQ(payload, jwt->payload); |
| |
| EXPECT_SOME_EQ(create_hs256_signature(payload), jwt->signature); |
| } |
| |
| // RS256 signed JWT. |
| { |
| const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey); |
| |
| ASSERT_SOME(jwt); |
| |
| EXPECT_EQ(JWT::Alg::RS256, jwt->header.alg); |
| EXPECT_SOME_EQ("JWT", jwt->header.typ); |
| |
| EXPECT_EQ(payload, jwt->payload); |
| |
| EXPECT_SOME_EQ(create_rs256_signature(payload), jwt->signature); |
| } |
| |
| // Unsecured JWT. |
| { |
| const Try<JWT, JWTError> jwt = JWT::create(payload); |
| |
| EXPECT_SOME(jwt); |
| |
| EXPECT_EQ(JWT::Alg::None, jwt->header.alg); |
| EXPECT_SOME_EQ("JWT", jwt->header.typ); |
| |
| EXPECT_EQ(payload, jwt->payload); |
| |
| EXPECT_NONE(jwt->signature); |
| } |
| } |
| |
| |
| TEST(JWTTest, Stringify) |
| { |
| // HS256. |
| { |
| JSON::Object payload; |
| payload.values["exp"] = 9999999999; |
| payload.values["sub"] = "foo"; |
| |
| const Try<JWT, JWTError> jwt = JWT::create(payload, "secret"); |
| |
| ASSERT_SOME(jwt); |
| |
| const string token = stringify(jwt.get()); |
| |
| EXPECT_EQ( |
| "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." |
| "eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9." |
| "7dwSK1mIRKqJTPQT8-AGnI-r8nnefw2hhai3kgBg7bs", |
| token); |
| } |
| |
| // RS256. |
| { |
| shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get(); |
| CHECK_NOTNULL(privateKey.get()); |
| |
| JSON::Object payload; |
| payload.values["exp"] = 9999999999; |
| payload.values["sub"] = "foo"; |
| |
| const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey); |
| |
| ASSERT_SOME(jwt); |
| |
| const string token = stringify(jwt.get()); |
| |
| EXPECT_EQ( |
| "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." |
| "eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9." |
| "kNVM5tQWMHCsKzxgoUYrQ-oNrRDaTdnXXT01_Kf3DG9rGAWegQ1GC9H" |
| "iKJr0Nces_C7kDg3xhg0TAKc4sumlRHnQf40Y6P6NGAw__71qTvCptb" |
| "NS97sQypPeI7iFGcZGg-WfO2e1u0ztbZZi0PnrSO_5TL4qPXNE0UZTw" |
| "Si3f8nOPbBoIDdXHZKBWDVbP7evgcsSTeg26i0kwNI3SMLFa0nUt3rw" |
| "BVflxaAPK2PDD16s6hEmg0EB9MXHXYQGmh2Q01G5o7XKWsAe5H46CWD" |
| "LnJFpU3NN4iGd4EkbN_wPjOQ0FjlzypCTqF0QRM0Stf219qwVIw4_rt" |
| "j8V4bZUdp-wg", |
| token); |
| } |
| } |