]> source.dussan.org Git - sonarqube.git/blob
08ad4f1a87d0634586451a853c44ade1bba3a097
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.alm.client.github.security;
21
22 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
23 import java.io.IOException;
24 import java.security.spec.InvalidKeySpecException;
25 import java.time.Clock;
26 import java.time.Instant;
27 import java.time.ZoneId;
28 import java.util.Random;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.sonar.auth.github.GithubAppConfiguration;
32
33 import static org.apache.commons.lang3.RandomStringUtils.secure;
34 import static org.assertj.core.api.Assertions.assertThat;
35 import static org.assertj.core.api.Assertions.assertThatThrownBy;
36
37 @RunWith(DataProviderRunner.class)
38 public class GithubAppSecurityImplTest {
39   private Clock clock = Clock.fixed(Instant.ofEpochSecond(132_600_888L), ZoneId.systemDefault());
40   private GithubAppSecurityImpl underTest = new GithubAppSecurityImpl(clock);
41
42   @Test
43   public void createAppToken_fails_with_IAE_if_privateKey_content_is_garbage() {
44     String garbage = secure().nextAlphanumeric(555);
45     GithubAppConfiguration githubAppConfiguration = createAppConfigurationForPrivateKey(garbage);
46
47     assertThatThrownBy(() -> underTest.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()))
48       .isInstanceOf(IllegalArgumentException.class)
49       .hasRootCauseMessage("Failed to decode Github Application private key");
50
51   }
52
53   @Test
54   public void createAppToken_fails_with_IAE_if_privateKey_PKCS8_content_is_missing_end_comment() {
55     String incompletePrivateKey = """
56       -----BEGIN RSA PRIVATE KEY-----
57       MIIEowIBAAKCAQEA6C29ZdvrwHOu7Eewv+xvUd4inCnACTzAHukHKTSY4R16+lRI
58       YC5qZ8Xo304J7lLhN4/d4Xnof3lDXZOHthVbJKik4fOuEGbTXTIcuFs3hdJtrJsb
59       antv8SOl5iR4fYRAf2AILMdtZI4iMSicBLIIttR+wVXo6NJYMjpj1OuAU3uN8eET
60       Gge09oJT3QOUBem7N8uaYi/p5uAfsf2/SVNsoMPV624X4kgNcyj/TMa6BosFJ8Y3
61       oeg0Aguk2yuHhAnixDVGoz6N7Go0QjEipVNix2JOOJwpFH4k2iZfM6n+8sJTLilq
62       yzT53JW/XI+M5AXVj4OjBJ/2yMPi3RFMNTdgRwIDAQABAoIBACcYBIsRI7oNAIgi
63       bh1y1y+mwpce5Inpo8PQovcKNy+4gguCg4lGZ34/sb1f64YoiGmNnOOpXj+QkIpC
64       HBjJscYTa2fsWwPB/Jb1qCZWnZu32eW1XEFqtWeaBAYjX/JqgV2xMs8vaTkEQbeb
65       SeH0hEkcsJcnOwdw247hjAu+96WWlyt10ZGgQaWPfXsdtelbaoaturNAVAJHdl9e
66       TIknCIbtLlbz/FtzjtCtdeiWr8gbKdVkshGtA8SKVhXGQwDwENjUkAUtSJ0aXR1t
67       +UjQcTISk7LiiYs0MrJ/CKoJ7mShwx7+YF3hgyqQ0qaqHwt9Yyd7wzWdCgdM5Eha
68       ccioIskCgYEA+EDJmcM5NGu5AYpZ1ogmG6jzsefAlr2NG1PQ/U03twal/B+ygAQb
69       5dholrq+aF+45Hrzfxije3Zrvpb08vxzKAs20lOlJsKftx2zkLR+mNvWTAORuO16
70       lG0c0cgYAKA1ld4R8KB8NmbuNb1w4LYZuyuFIEVmm2B3ca141WNHBwMCgYEA72yK
71       B4+xxomZn6dtbCGQZxziaI9WH/KEfDemKO5cfPlynQjmmMkiDpcyHa7mvdU+PGh3
72       g+OmQxORXMmBkHEnYS1fl3ac3U5sLiHAQBmTKKcLuVQlIU4oDu/K6WEGL9DdPtaK
73       gyOOWtSnfHTbT0bZ4IMm+gzdc4bCuEjvYyUhzG0CgYAEN011MAyTqFSvAwN9kjhb
74       deYVmmL57GQuF6FP+/S7RgChpIQqimdS4vb7wFYlfaKtNq1V9jwoh51S0kt8qO7n
75       ujEHJ2aBnwKJYJbBGV+hBvK/vbvG0TmotaWspmJJ+G6QigHx/Te+0Maw4PO+zTjo
76       pdeP8b3JW70LkC+iKBp3swKBgFL/nm32m1tHEjFtehpVHFkSg05Z+jJDATiKlhh0
77       YS2Vz+yuTDpE54CFW4M8wZKnXNbWJDBdd6KjIu42kKrA/zTJ5Ox92u1BJXFsk9fk
78       xcX++qp5iBGepXZgHEiBMQLcdgY1m3jQl6XXOGSFog0+c4NIE/f1A8PrwI7gAdSt
79       56SVAoGBAJp214Fo0oheMTTYKVtXuGiH/v3JNG1jKFgsmHqndf4wy7U6bbNctEzc
80       ZXNIacuhWmko6YejMrWNhE57sX812MhXGZq6y0sYZGKtp7oDv8G3rWD6bpZywpcV
81       kTtMJxm8J64u6bAkpWG3BocJP9qbXeAbILo1wuXgYqABBrpA9nnc""";
82     GithubAppConfiguration githubAppConfiguration = createAppConfigurationForPrivateKey(incompletePrivateKey);
83
84     assertThatThrownBy(() -> underTest.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()))
85       .isInstanceOf(IllegalArgumentException.class)
86       .hasRootCauseInstanceOf(IOException.class)
87       .hasRootCauseMessage("-----END RSA PRIVATE KEY----- not found");
88   }
89
90   @Test
91   public void createAppToken_fails_with_IAE_if_privateKey_PKCS8_content_is_corrupted() {
92     String corruptedPrivateKey = """
93       -----BEGIN RSA PRIVATE KEY-----
94       //This+Key+Is+Corrupted+Gets+Decoded+But+It+Is+Invalid+pXj+QkIpC
95       MIIEowIBAAKCAQEA6C29ZdvrwHOu7Eewv+xvUd4inCnACTzAHukHKTSY4R16+lRI
96       YC5qZ8Xo304J7lLhN4/d4Xnof3lDXZOHthVbJKik4fOuEGbTXTIcuFs3hdJtrJsb
97       antv8SOl5iR4fYRAf2AILMdtZI4iMSicBLIIttR+wVXo6NJYMjpj1OuAU3uN8eET
98       Gge09oJT3QOUBem7N8uaYi/p5uAfsf2/SVNsoMPV624X4kgNcyj/TMa6BosFJ8Y3
99       oeg0Aguk2yuHhAnixDVGoz6N7Go0QjEipVNix2JOOJwpFH4k2iZfM6n+8sJTLilq
100       yzT53JW/XI+M5AXVj4OjBJ/2yMPi3RFMNTdgRwIDAQABAoIBACcYBIsRI7oNAIgi
101       HBjJscYTa2fsWwPB/Jb1qCZWnZu32eW1XEFqtWeaBAYjX/JqgV2xMs8vaTkEQbeb
102       g+OmQxORXMmBkHEnYS1fl3ac3U5sLiHAQBmTKKcLuVQlIU4oDu/K6WEGL9DdPtaK
103       gyOOWtSnfHTbT0bZ4IMm+gzdc4bCuEjvYyUhzG0CgYAEN011MAyTqFSvAwN9kjhb
104       deYVmmL57GQuF6FP+/S7RgChpIQqimdS4vb7wFYlfaKtNq1V9jwoh51S0kt8qO7n
105       ujEHJ2aBnwKJYJbBGV+hBvK/vbvG0TmotaWspmJJ+G6QigHx/Te+0Maw4PO+zTjo
106       pdeP8b3JW70LkC+iKBp3swKBgFL/nm32m1tHEjFtehpVHFkSg05Z+jJDATiKlhh0
107       YS2Vz+yuTDpE54CFW4M8wZKnXNbWJDBdd6KjIu42kKrA/zTJ5Ox92u1BJXFsk9fk
108       xcX++qp5iBGepXZgHEiBMQLcdgY1m3jQl6XXOGSFog0+c4NIE/f1A8PrwI7gAdSt
109       56SVAoGBAJp214Fo0oheMTTYKVtXuGiH/v3JNG1jKFgsmHqndf4wy7U6bbNctEzc
110       ZXNIacuhWmko6YejMrWNhE57sX812MhXGZq6y0sYZGKtp7oDv8G3rWD6bpZywpcV
111       kTtMJxm8J64u6bAkpWG3BocJP9qbXeAbILo1wuXgYqABBrpA9nnc
112       -----END RSA PRIVATE KEY-----""";
113     GithubAppConfiguration githubAppConfiguration = createAppConfigurationForPrivateKey(corruptedPrivateKey);
114
115     assertThatThrownBy(() -> underTest.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()))
116       .isInstanceOf(IllegalArgumentException.class)
117       .hasCauseInstanceOf(InvalidKeySpecException.class);
118   }
119
120   @Test
121   public void getApplicationJWTToken_throws_ISE_if_conf_is_not_complete() {
122     GithubAppConfiguration githubAppConfiguration = createAppConfiguration(false);
123     assertThatThrownBy(() -> underTest.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()))
124       .isInstanceOf(IllegalStateException.class);
125   }
126
127   @Test
128   public void getApplicationJWTToken_returns_token_if_app_config_and_private_key_are_valid() {
129     GithubAppConfiguration githubAppConfiguration = createAppConfiguration(true);
130
131     assertThat(underTest.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).isNotNull();
132   }
133
134   private GithubAppConfiguration createAppConfiguration(boolean validConfiguration) {
135     if (validConfiguration) {
136       return createAppConfiguration();
137     } else {
138       return new GithubAppConfiguration(null, null, null);
139     }
140   }
141
142   private GithubAppConfiguration createAppConfiguration() {
143     return new GithubAppConfiguration(1L, REAL_PRIVATE_KEY, secure().nextAlphanumeric(5));
144   }
145
146   private GithubAppConfiguration createAppConfigurationForPrivateKey(String privateKey) {
147     long applicationId = new Random().nextInt(654);
148     return new GithubAppConfiguration(applicationId, privateKey, secure().nextAlphabetic(8));
149   }
150
151   private static final String REAL_PRIVATE_KEY = """
152     -----BEGIN RSA PRIVATE KEY-----
153     MIIEowIBAAKCAQEA6C29ZdvrwHOu7Eewv+xvUd4inCnACTzAHukHKTSY4R16+lRI
154     YC5qZ8Xo304J7lLhN4/d4Xnof3lDXZOHthVbJKik4fOuEGbTXTIcuFs3hdJtrJsb
155     antv8SOl5iR4fYRAf2AILMdtZI4iMSicBLIIttR+wVXo6NJYMjpj1OuAU3uN8eET
156     Gge09oJT3QOUBem7N8uaYi/p5uAfsf2/SVNsoMPV624X4kgNcyj/TMa6BosFJ8Y3
157     oeg0Aguk2yuHhAnixDVGoz6N7Go0QjEipVNix2JOOJwpFH4k2iZfM6n+8sJTLilq
158     yzT53JW/XI+M5AXVj4OjBJ/2yMPi3RFMNTdgRwIDAQABAoIBACcYBIsRI7oNAIgi
159     bh1y1y+mwpce5Inpo8PQovcKNy+4gguCg4lGZ34/sb1f64YoiGmNnOOpXj+QkIpC
160     HBjJscYTa2fsWwPB/Jb1qCZWnZu32eW1XEFqtWeaBAYjX/JqgV2xMs8vaTkEQbeb
161     SeH0hEkcsJcnOwdw247hjAu+96WWlyt10ZGgQaWPfXsdtelbaoaturNAVAJHdl9e
162     TIknCIbtLlbz/FtzjtCtdeiWr8gbKdVkshGtA8SKVhXGQwDwENjUkAUtSJ0aXR1t
163     +UjQcTISk7LiiYs0MrJ/CKoJ7mShwx7+YF3hgyqQ0qaqHwt9Yyd7wzWdCgdM5Eha
164     ccioIskCgYEA+EDJmcM5NGu5AYpZ1ogmG6jzsefAlr2NG1PQ/U03twal/B+ygAQb
165     5dholrq+aF+45Hrzfxije3Zrvpb08vxzKAs20lOlJsKftx2zkLR+mNvWTAORuO16
166     lG0c0cgYAKA1ld4R8KB8NmbuNb1w4LYZuyuFIEVmm2B3ca141WNHBwMCgYEA72yK
167     B4+xxomZn6dtbCGQZxziaI9WH/KEfDemKO5cfPlynQjmmMkiDpcyHa7mvdU+PGh3
168     g+OmQxORXMmBkHEnYS1fl3ac3U5sLiHAQBmTKKcLuVQlIU4oDu/K6WEGL9DdPtaK
169     gyOOWtSnfHTbT0bZ4IMm+gzdc4bCuEjvYyUhzG0CgYAEN011MAyTqFSvAwN9kjhb
170     deYVmmL57GQuF6FP+/S7RgChpIQqimdS4vb7wFYlfaKtNq1V9jwoh51S0kt8qO7n
171     ujEHJ2aBnwKJYJbBGV+hBvK/vbvG0TmotaWspmJJ+G6QigHx/Te+0Maw4PO+zTjo
172     pdeP8b3JW70LkC+iKBp3swKBgFL/nm32m1tHEjFtehpVHFkSg05Z+jJDATiKlhh0
173     YS2Vz+yuTDpE54CFW4M8wZKnXNbWJDBdd6KjIu42kKrA/zTJ5Ox92u1BJXFsk9fk
174     xcX++qp5iBGepXZgHEiBMQLcdgY1m3jQl6XXOGSFog0+c4NIE/f1A8PrwI7gAdSt
175     56SVAoGBAJp214Fo0oheMTTYKVtXuGiH/v3JNG1jKFgsmHqndf4wy7U6bbNctEzc
176     ZXNIacuhWmko6YejMrWNhE57sX812MhXGZq6y0sYZGKtp7oDv8G3rWD6bpZywpcV
177     kTtMJxm8J64u6bAkpWG3BocJP9qbXeAbILo1wuXgYqABBrpA9nnc
178     -----END RSA PRIVATE KEY-----""";
179 }