summaryrefslogtreecommitdiffstats
path: root/javatests/com/google/gerrit/server/mail/SignedTokenTest.java
blob: 27c4f56da9ae36c11799aafee40bfc521ab6bde9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright (C) 2020 The Android Open Source Project
//
// 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.

package com.google.gerrit.server.mail;

import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;

import java.util.Random;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;

public class SignedTokenTest {

  private static final Pattern URL_UNSAFE_CHARS = Pattern.compile("(\\+|/)");
  private static final String REGISTER_EMAIL_PRIVATE_KEY = "TGMv3/bTC42jUKQndTQrXyHhHYMP0t69i/4=";
  private static final int maxAge = 5;
  private static final String TEXT = "This is a text";
  private static final String FORGED_TEXT = "This is a forged text";
  private static final String FORGED_TOKEN = String.format("Zm9yZ2VkJTIwa2V5$%s", TEXT);

  private SignedToken signedToken;

  @Before
  public void setUp() throws Exception {
    signedToken = new SignedToken(maxAge, REGISTER_EMAIL_PRIVATE_KEY);
  }

  /** Test new token: the key is a normal BASE64 string that can be used for URL safely */
  @Test
  public void newTokenKeyDoesNotContainUnsafeChar() throws Exception {
    assertThat(signedToken.newToken(TEXT)).doesNotContainMatch(URL_UNSAFE_CHARS);
  }

  /** Test new token: the key is an URL unsafe BASE64 string with index of '62'(+) */
  @Test
  public void newTokenWithUrlUnsafeBase64Plus() throws Exception {
    String token = "+" + signedToken.newToken(TEXT);
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(token, TEXT));

    assertThat(thrown).hasMessageThat().contains("decoding failed");

    assertThat(thrown)
        .hasCauseThat()
        .hasMessageThat()
        .isEqualTo(
            "com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: +");
  }

  /** Test new token: the key is an URL unsafe BASE64 string with '63'(/) */
  @Test
  public void newTokenWithUrlUnsafeBase64Slash() throws Exception {
    String token = "/" + signedToken.newToken(TEXT);
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(token, TEXT));

    assertThat(thrown).hasMessageThat().contains("decoding failed");

    assertThat(thrown)
        .hasCauseThat()
        .hasMessageThat()
        .isEqualTo(
            "com.google.common.io.BaseEncoding$DecodingException: Unrecognized character: /");
  }

  /** Test check token: BASE64 encoding and decoding in a safe URL way */
  @Test
  public void checkToken() throws Exception {
    String token = signedToken.newToken(TEXT);
    ValidToken validToken = signedToken.checkToken(token, TEXT);
    assertThat(validToken).isNotNull();
    assertThat(validToken.getData()).isEqualTo(TEXT);
  }

  /** Test check token: input token string is null */
  @Test
  public void checkTokenInputTokenNull() throws Exception {
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(null, TEXT));

    assertThat(thrown).hasMessageThat().isEqualTo("Empty token");
  }

  /** Test check token: input token string is empty */
  @Test
  public void checkTokenInputTokenEmpty() throws Exception {
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken("", TEXT));

    assertThat(thrown).hasMessageThat().isEqualTo("Empty token");
  }

  /** Test check token: token string is not illegal with no '$' character */
  @Test
  public void checkTokenInputTokenNoDollarSplitChar() throws Exception {
    String token = signedToken.newToken(TEXT).replace("$", "¥");
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(token, TEXT));

    assertThat(thrown).hasMessageThat().isEqualTo("Token does not contain character '$'");
  }

  /** Test check token: token string length is match but is not a legal BASE64 string */
  @Test
  public void checkTokenInputTokenKeyBase64DecodeFail() throws Exception {
    String token = signedToken.newToken(TEXT);
    String key = randomString(token.indexOf("$") + 1);
    String illegalBase64Token = key + "$" + TEXT;
    CheckTokenException thrown =
        assertThrows(
            CheckTokenException.class, () -> signedToken.checkToken(illegalBase64Token, TEXT));

    assertThat(thrown).hasMessageThat().isEqualTo("Base64 decoding failed");
  }

  /** Test check token: token is illegal with a forged key */
  @Test
  public void checkTokenForgedKey() throws Exception {
    CheckTokenException thrown =
        assertThrows(CheckTokenException.class, () -> signedToken.checkToken(FORGED_TOKEN, TEXT));

    assertThat(thrown).hasMessageThat().isEqualTo("Token length mismatch");
  }

  /** Test check token: token is illegal with a forged text */
  @Test
  public void checkTokenForgedText() throws Exception {
    CheckTokenException thrown =
        assertThrows(
            CheckTokenException.class,
            () -> {
              String token = signedToken.newToken(TEXT);
              signedToken.checkToken(token, FORGED_TEXT);
            });

    assertThat(thrown).hasMessageThat().isEqualTo("Token text mismatch");
  }

  private static String randomString(int length) {
    String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    Random random = new Random();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < length; i++) {
      int number = random.nextInt(62);
      sb.append(str.charAt(number));
    }
    return sb.toString();
  }
}