|
1 | 1 | package com.symphony.bdk.core.auth; |
2 | 2 |
|
3 | | -import static com.symphony.bdk.core.util.DeprecationLogger.logDeprecation; |
4 | | -import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; |
5 | | - |
6 | 3 | import com.symphony.bdk.core.auth.exception.AuthInitializationException; |
7 | | -import com.symphony.bdk.core.auth.impl.BotAuthenticatorCertImpl; |
8 | | -import com.symphony.bdk.core.auth.impl.BotAuthenticatorRsaImpl; |
9 | | -import com.symphony.bdk.core.auth.impl.ExtensionAppAuthenticatorCertImpl; |
10 | | -import com.symphony.bdk.core.auth.impl.ExtensionAppAuthenticatorRsaImpl; |
11 | | -import com.symphony.bdk.core.auth.impl.InMemoryTokensRepository; |
12 | | -import com.symphony.bdk.core.auth.impl.OboAuthenticatorCertImpl; |
13 | | -import com.symphony.bdk.core.auth.impl.OboAuthenticatorRsaImpl; |
14 | | -import com.symphony.bdk.core.auth.jwt.JwtHelper; |
15 | | -import com.symphony.bdk.core.client.ApiClientFactory; |
16 | | -import com.symphony.bdk.core.config.model.BdkAuthenticationConfig; |
17 | | -import com.symphony.bdk.core.config.model.BdkConfig; |
18 | | - |
19 | | -import com.symphony.bdk.core.service.version.AgentVersionService; |
20 | | - |
21 | | -import com.symphony.bdk.gen.api.SignalsApi; |
22 | 4 |
|
23 | | -import lombok.extern.slf4j.Slf4j; |
24 | | -import org.apache.commons.io.IOUtils; |
25 | 5 | import org.apiguardian.api.API; |
26 | 6 |
|
27 | | -import java.io.FileInputStream; |
28 | | -import java.io.IOException; |
29 | | -import java.io.InputStream; |
30 | | -import java.nio.charset.StandardCharsets; |
31 | | -import java.security.GeneralSecurityException; |
32 | | -import java.security.PrivateKey; |
33 | | - |
34 | 7 | import javax.annotation.Nonnull; |
35 | 8 |
|
36 | 9 | /** |
37 | | - * Factory class that provides new instances for the main authenticators : |
| 10 | + * Factory responsible for creating different authenticators. |
38 | 11 | * <ul> |
39 | | - * <li>{@link BotAuthenticator} : to authenticate the main Bot service account</li> |
40 | | - * <li>{@link OboAuthenticator} : to perform on-behalf-of authentication</li> |
| 12 | + * <li>Bot Authenticator: for authenticating the main bot service account</li> |
| 13 | + * <li>OBO Authenticator: for authenticating on behalf of a regular Symphony user</li> |
| 14 | + * <li>Extension App Authenticator: for authenticating an extension application</li> |
41 | 15 | * </ul> |
42 | 16 | */ |
43 | | -@Slf4j |
44 | 17 | @API(status = API.Status.STABLE) |
45 | | -public class AuthenticatorFactory { |
46 | | - |
47 | | - private final BdkConfig config; |
48 | | - private final ApiClientFactory apiClientFactory; |
49 | | - private final ExtensionAppTokensRepository extensionAppTokensRepository; |
50 | | - |
51 | | - public AuthenticatorFactory(@Nonnull BdkConfig bdkConfig, @Nonnull ApiClientFactory apiClientFactory) { |
52 | | - this(bdkConfig, apiClientFactory, new InMemoryTokensRepository()); |
53 | | - } |
54 | | - |
55 | | - public AuthenticatorFactory(@Nonnull BdkConfig bdkConfig, @Nonnull ApiClientFactory apiClientFactory, |
56 | | - @Nonnull ExtensionAppTokensRepository extensionAppTokensRepository) { |
57 | | - this.config = bdkConfig; |
58 | | - this.apiClientFactory = apiClientFactory; |
59 | | - this.extensionAppTokensRepository = extensionAppTokensRepository; |
60 | | - } |
| 18 | +public interface AuthenticatorFactory { |
61 | 19 |
|
62 | 20 | /** |
63 | | - * Creates a new instance of a {@link BotAuthenticator} service. |
| 21 | + * Creates a new instance of a {@link BotAuthenticator}. |
64 | 22 | * |
65 | 23 | * @return a new {@link BotAuthenticator} instance. |
| 24 | + * @throws AuthInitializationException if the authenticator cannot be instantiated. |
66 | 25 | */ |
67 | | - public @Nonnull |
68 | | - BotAuthenticator getBotAuthenticator() throws AuthInitializationException { |
69 | | - if (this.config.getBot().isBothCertificateAndRsaConfigured()) { |
70 | | - throw new AuthInitializationException( |
71 | | - "Both of certificate and rsa authentication are configured. Only one of them should be provided."); |
72 | | - } |
73 | | - if (this.config.getBot().isCertificateAuthenticationConfigured()) { |
74 | | - if (!this.config.getBot().isCertificateConfigurationValid()) { |
75 | | - throw new AuthInitializationException( |
76 | | - "Only one of certificate path or content should be configured for bot authentication."); |
77 | | - } |
78 | | - return new BotAuthenticatorCertImpl( |
79 | | - this.config.getRetry(), |
80 | | - this.config.getBot().getUsername(), |
81 | | - this.config.getCommonJwt(), |
82 | | - this.apiClientFactory.getLoginClient(), |
83 | | - this.apiClientFactory.getSessionAuthClient(), |
84 | | - this.apiClientFactory.getKeyAuthClient(), |
85 | | - new AgentVersionService(new SignalsApi(this.apiClientFactory.getAgentClient())) |
86 | | - ); |
87 | | - } |
88 | | - if (this.config.getBot().isRsaAuthenticationConfigured()) { |
89 | | - if (!this.config.getBot().isRsaConfigurationValid()) { |
90 | | - throw new AuthInitializationException( |
91 | | - "Only one of private key path or content should be configured for bot authentication."); |
92 | | - } |
93 | | - return new BotAuthenticatorRsaImpl( |
94 | | - this.config.getRetry(), |
95 | | - this.config.getBot().getUsername(), |
96 | | - this.config.getCommonJwt(), |
97 | | - this.loadPrivateKeyFromAuthenticationConfig(this.config.getBot()), |
98 | | - this.apiClientFactory.getLoginClient(), |
99 | | - this.apiClientFactory.getRelayClient(), |
100 | | - new AgentVersionService(new SignalsApi(this.apiClientFactory.getAgentClient())) |
101 | | - ); |
102 | | - } |
103 | | - throw new AuthInitializationException("Neither RSA private key nor certificate is configured."); |
104 | | - } |
| 26 | + @Nonnull |
| 27 | + BotAuthenticator getBotAuthenticator() throws AuthInitializationException; |
105 | 28 |
|
106 | 29 | /** |
107 | | - * Creates a new instance of an {@link OboAuthenticator} service. |
| 30 | + * Creates a new instance of a {@link OboAuthenticator}. |
108 | 31 | * |
109 | 32 | * @return a new {@link OboAuthenticator} instance. |
| 33 | + * @throws AuthInitializationException if the authenticator cannot be instantiated. |
110 | 34 | */ |
111 | | - public @Nonnull |
112 | | - OboAuthenticator getOboAuthenticator() throws AuthInitializationException { |
113 | | - if (this.config.getApp().isBothCertificateAndRsaConfigured()) { |
114 | | - throw new AuthInitializationException( |
115 | | - "Both of certificate and rsa authentication are configured. Only one of them should be provided."); |
116 | | - } |
117 | | - if (this.config.getApp().isCertificateAuthenticationConfigured()) { |
118 | | - if (!this.config.getApp().isCertificateConfigurationValid()) { |
119 | | - throw new AuthInitializationException( |
120 | | - "Only one of certificate path or content should be configured for app authentication."); |
121 | | - } |
122 | | - return new OboAuthenticatorCertImpl( |
123 | | - this.config.getRetry(), |
124 | | - this.config.getApp().getAppId(), |
125 | | - this.apiClientFactory.getExtAppSessionAuthClient() |
126 | | - ); |
127 | | - } |
128 | | - if (this.config.getApp().isRsaAuthenticationConfigured()) { |
129 | | - if (!this.config.getApp().isRsaConfigurationValid()) { |
130 | | - throw new AuthInitializationException( |
131 | | - "Only one of private key path or content should be configured for app authentication."); |
132 | | - } |
133 | | - return new OboAuthenticatorRsaImpl( |
134 | | - this.config.getRetry(), |
135 | | - this.config.getApp().getAppId(), |
136 | | - this.loadPrivateKeyFromAuthenticationConfig(this.config.getApp()), |
137 | | - this.apiClientFactory.getLoginClient() |
138 | | - ); |
139 | | - } |
140 | | - throw new AuthInitializationException("Neither RSA private key nor certificate is configured."); |
141 | | - } |
| 35 | + @Nonnull |
| 36 | + OboAuthenticator getOboAuthenticator() throws AuthInitializationException; |
142 | 37 |
|
143 | 38 | /** |
144 | | - * Creates a new instance of an {@link ExtensionAppAuthenticator} service. |
| 39 | + * Creates a new instance of a {@link ExtensionAppAuthenticator}. |
145 | 40 | * |
146 | 41 | * @return a new {@link ExtensionAppAuthenticator} instance. |
| 42 | + * @throws AuthInitializationException if the authenticator cannot be instantiated. |
147 | 43 | */ |
148 | | - public @Nonnull |
149 | | - ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializationException { |
150 | | - if (this.config.getApp().isBothCertificateAndRsaConfigured()) { |
151 | | - throw new AuthInitializationException( |
152 | | - "Both of certificate and rsa authentication are configured. Only one of them should be provided."); |
153 | | - } |
154 | | - if (this.config.getApp().isCertificateAuthenticationConfigured()) { |
155 | | - if (!this.config.getApp().isCertificateConfigurationValid()) { |
156 | | - throw new AuthInitializationException( |
157 | | - "Only one of certificate path or content should be configured for app authentication."); |
158 | | - } |
159 | | - return new ExtensionAppAuthenticatorCertImpl( |
160 | | - this.config.getRetry(), |
161 | | - this.config.getApp().getAppId(), |
162 | | - this.apiClientFactory.getExtAppSessionAuthClient(), |
163 | | - extensionAppTokensRepository); |
164 | | - } |
165 | | - if (this.config.getApp().isRsaAuthenticationConfigured()) { |
166 | | - if (!this.config.getApp().isRsaConfigurationValid()) { |
167 | | - throw new AuthInitializationException( |
168 | | - "Only one of private key path or content should be configured for app authentication."); |
169 | | - } |
170 | | - return new ExtensionAppAuthenticatorRsaImpl( |
171 | | - this.config.getRetry(), |
172 | | - this.config.getApp().getAppId(), |
173 | | - this.loadPrivateKeyFromAuthenticationConfig(this.config.getApp()), |
174 | | - this.apiClientFactory.getLoginClient(), |
175 | | - this.apiClientFactory.getPodClient(), |
176 | | - extensionAppTokensRepository |
177 | | - ); |
178 | | - } |
179 | | - throw new AuthInitializationException("Neither RSA private key nor certificate is configured."); |
180 | | - } |
181 | | - |
182 | | - private PrivateKey loadPrivateKeyFromAuthenticationConfig(BdkAuthenticationConfig config) |
183 | | - throws AuthInitializationException { |
184 | | - String privateKeyPath = ""; |
185 | | - try { |
186 | | - String privateKey; |
187 | | - if (config.getPrivateKey() != null && config.getPrivateKey().isConfigured()) { |
188 | | - if (isNotEmpty(config.getPrivateKey().getContent())) { |
189 | | - privateKey = new String(config.getPrivateKey().getContent(), StandardCharsets.UTF_8); |
190 | | - } else { |
191 | | - privateKeyPath = config.getPrivateKey().getPath(); |
192 | | - log.debug("Loading RSA privateKey from path : {}", privateKeyPath); |
193 | | - privateKey = loadPrivateKey(privateKeyPath); |
194 | | - } |
195 | | - } else { |
196 | | - logDeprecation("RSA private key should be configured under \"privateKey\" field"); |
197 | | - if (isNotEmpty(config.getPrivateKeyContent())) { |
198 | | - privateKey = new String(config.getPrivateKeyContent(), StandardCharsets.UTF_8); |
199 | | - } else { |
200 | | - privateKeyPath = config.getPrivateKeyPath(); |
201 | | - log.debug("Loading RSA privateKey from path : {}", privateKeyPath); |
202 | | - privateKey = loadPrivateKey(privateKeyPath); |
203 | | - } |
204 | | - } |
205 | | - return JwtHelper.parseRsaPrivateKey(privateKey); |
206 | | - } catch (GeneralSecurityException e) { |
207 | | - final String message = String.format("Unable to parse RSA Private Key from path %s. Check if the format is " |
208 | | - + "correct.", privateKeyPath); |
209 | | - throw new AuthInitializationException(message, e); |
210 | | - } catch (IOException e) { |
211 | | - final String message = "Unable to read or find RSA Private Key from path " + privateKeyPath; |
212 | | - throw new AuthInitializationException(message, e); |
213 | | - } |
214 | | - } |
215 | | - |
216 | | - private static String loadPrivateKey(String privateKeyPath) throws IOException, AuthInitializationException { |
217 | | - InputStream is; |
218 | | - |
219 | | - // useful for testing when private key is located into resources |
220 | | - if (privateKeyPath.startsWith("classpath:")) { |
221 | | - log.warn("Warning: Keeping RSA private keys into project resources is dangerous. " |
222 | | - + "You should consider another location for production."); |
223 | | - is = AuthenticatorFactory.class.getResourceAsStream(privateKeyPath.replace("classpath:", "")); |
224 | | - if (is == null) { |
225 | | - throw new AuthInitializationException( |
226 | | - "Unable to find RSA private key as classpath resource from: " + privateKeyPath); |
227 | | - } |
228 | | - try (InputStream resourceStream = is) { |
229 | | - return IOUtils.toString(resourceStream, StandardCharsets.UTF_8); |
230 | | - } |
231 | | - } else { |
232 | | - try (InputStream fileStream = new FileInputStream(privateKeyPath)) { |
233 | | - return IOUtils.toString(fileStream, StandardCharsets.UTF_8); |
234 | | - } |
235 | | - } |
236 | | - } |
| 44 | + @Nonnull |
| 45 | + ExtensionAppAuthenticator getExtensionAppAuthenticator() throws AuthInitializationException; |
237 | 46 | } |
0 commit comments