Update oauth-provider to use new OSGi APIs
[ccsdk/features.git] / sdnr / wt / oauth-provider / provider-jar / src / main / java / org / onap / ccsdk / features / sdnr / wt / oauthprovider / data / Config.java
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2020 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.oauthprovider.data;
23
24 import com.fasterxml.jackson.annotation.JsonGetter;
25 import com.fasterxml.jackson.annotation.JsonIgnore;
26 import com.fasterxml.jackson.annotation.JsonSetter;
27 import java.io.File;
28 import java.io.FileNotFoundException;
29 import java.io.IOException;
30 import java.nio.file.Files;
31 import java.security.SecureRandom;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 public class Config {
40
41     private static final Logger LOG = LoggerFactory.getLogger(Config.class);
42     private static final String DEFAULT_CONFIGFILENAME = "etc/oauth-provider.config.json";
43     private static final String ENVVARIABLE = "${";
44     private static final String REGEXENVVARIABLE = "(\\$\\{[A-Z0-9_-]+\\})";
45     private static final Pattern pattern = Pattern.compile(REGEXENVVARIABLE);
46     private static final String DEFAULT_TOKENISSUER = "Opendaylight";
47     private static final String DEFAULT_TOKENSECRET = generateSecret();
48     private static final String DEFAULT_REDIRECTURI = "/odlux/index.html#/oauth?token=";
49     private static final String DEFAULT_SUPPORTODLUSERS = "true";
50     public static final String TOKENALG_HS256 = "HS256";
51     public static final String TOKENALG_RS256 = "RS256";
52     public static final String TOKENALG_RS512 = "RS512";
53     private static final String CLIENTALG_PRE = "Client";
54     public static final String TOKENALG_CLIENT_RS256 = CLIENTALG_PRE + TOKENALG_RS256;
55     public static final String TOKENALG_CLIENT_RS512 = CLIENTALG_PRE + TOKENALG_RS512;
56     private static final String DEFAULT_TOKEN_ALGORITHM = TOKENALG_HS256;
57
58     private static final long DEFAULT_TOKEN_LIFETIME = 30 * 60;
59     private static final List<String> VALID_ALGORITHMS =
60             Arrays.asList(TOKENALG_HS256, TOKENALG_RS256, TOKENALG_RS512, TOKENALG_CLIENT_RS256, TOKENALG_CLIENT_RS512);
61     private static final List<String> VALID_ALGORITHMS_FOR_INTERNAL_LOGIN =
62             Arrays.asList(TOKENALG_HS256, TOKENALG_RS256, TOKENALG_RS512);
63     private static SecureRandom random;
64     private static Config _instance;
65
66     private List<OAuthProviderConfig> providers;
67     private String redirectUri;
68     private String supportOdlUsers;
69     private String tokenSecret;
70     private String tokenPubKey;
71     private String algorithm;
72     private String tokenIssuer;
73     private String publicUrl;
74     private long tokenLifetime;
75
76     @Override
77     public String toString() {
78         return "Config [providers=" + providers + ", redirectUri=" + redirectUri + ", supportOdlUsers="
79                 + supportOdlUsers + ", tokenSecret=***, tokenPubKey=" + tokenPubKey + ", algorithm=" + algorithm
80                 + ", tokenIssuer=" + tokenIssuer + ", publicUrl=" + publicUrl + ", tokenLifetime=" + tokenLifetime
81                 + "]";
82     }
83
84     public List<OAuthProviderConfig> getProviders() {
85         return providers;
86     }
87
88     public void setProviders(List<OAuthProviderConfig> providers) {
89         this.providers = providers;
90     }
91
92     public String getRedirectUri() {
93         return redirectUri;
94     }
95
96     public void setRedirectUri(String redirectUri) {
97         this.redirectUri = redirectUri;
98     }
99
100     public String getSupportOdlUsers() {
101         return supportOdlUsers;
102     }
103
104     public void setSupportOdlUsers(String supportOdlUsers) {
105         this.supportOdlUsers = supportOdlUsers;
106     }
107
108     public String getTokenSecret() {
109         return tokenSecret;
110     }
111
112     public void setTokenSecret(String tokenSecret) {
113         this.tokenSecret = tokenSecret;
114     }
115
116     public String getAlgorithm() {
117         return this.algorithm;
118     }
119
120     public void setAlgorithm(String alg) {
121         this.algorithm = alg;
122     }
123
124     @JsonGetter("tokenPubKey")
125     public String getPublicKey() {
126         return this.tokenPubKey;
127     }
128
129     @JsonSetter("tokenPubKey")
130     public void setPublicKey(String pubKey) {
131         this.tokenPubKey = pubKey;
132     }
133
134     public String getTokenIssuer() {
135         return tokenIssuer;
136     }
137
138     public void setTokenIssuer(String tokenIssuer) {
139         this.tokenIssuer = tokenIssuer;
140     }
141
142     public String getPublicUrl() {
143         return publicUrl;
144     }
145
146     public void setPublicUrl(String publicUrl) {
147         this.publicUrl = publicUrl;
148     }
149
150     public long getTokenLifetime() {
151         return this.tokenLifetime;
152     }
153
154     public void setTokenLifetime(long lifetime) {
155         this.tokenLifetime = lifetime;
156     }
157
158     @JsonIgnore
159     private void handleEnvironmentVars() {
160         if (isEnvExpression(this.tokenIssuer)) {
161             this.tokenIssuer = getProperty(this.tokenIssuer, null);
162         }
163         if (isEnvExpression(this.tokenSecret)) {
164             this.tokenSecret = getProperty(this.tokenSecret, null);
165         }
166         if (isEnvExpression(this.tokenPubKey)) {
167             this.tokenPubKey = getProperty(this.tokenPubKey, null);
168         }
169         if (isEnvExpression(this.algorithm)) {
170             this.algorithm = getProperty(this.algorithm, null);
171         }
172         if (isEnvExpression(this.publicUrl)) {
173             this.publicUrl = getProperty(this.publicUrl, null);
174         }
175         if (isEnvExpression(this.redirectUri)) {
176             this.redirectUri = getProperty(this.redirectUri, null);
177         }
178         if (isEnvExpression(this.supportOdlUsers)) {
179             this.supportOdlUsers = getProperty(this.supportOdlUsers, null);
180         }
181         if (this.providers != null && !this.providers.isEmpty()) {
182             for (OAuthProviderConfig cfg : this.providers) {
183                 cfg.handleEnvironmentVars();
184             }
185         }
186     }
187
188     @JsonIgnore
189     private void handleDefaultValues() {
190         if (this.tokenIssuer == null || this.tokenIssuer.isEmpty()) {
191             this.tokenIssuer = DEFAULT_TOKENISSUER;
192         }
193         if (this.algorithm == null || this.algorithm.isEmpty()) {
194             this.algorithm = DEFAULT_TOKEN_ALGORITHM;
195         }
196         if (TOKENALG_HS256.equals(this.algorithm) && (this.tokenSecret == null || this.tokenSecret.isEmpty())) {
197             this.tokenSecret = DEFAULT_TOKENSECRET;
198         }
199         if (this.redirectUri == null || this.redirectUri.isEmpty() || "null".equals(this.redirectUri)) {
200             this.redirectUri = DEFAULT_REDIRECTURI;
201         }
202         if (this.publicUrl != null && (this.publicUrl.isEmpty() || "null".equals(this.publicUrl))) {
203             this.publicUrl = null;
204         }
205         if (this.supportOdlUsers == null || this.supportOdlUsers.isEmpty()) {
206             this.supportOdlUsers = DEFAULT_SUPPORTODLUSERS;
207         }
208         if (this.tokenLifetime <= 0) {
209             this.tokenLifetime = DEFAULT_TOKEN_LIFETIME;
210         }
211     }
212
213     static boolean isEnvExpression(String key) {
214         return key != null && key.contains(ENVVARIABLE);
215     }
216
217     public static String generateSecret() {
218         return generateSecret(30);
219     }
220
221     public static String generateSecret(int targetStringLength) {
222         int leftLimit = 48; // numeral '0'
223         int rightLimit = 122; // letter 'z'
224         if (random == null) {
225             random = new SecureRandom();
226         }
227         String generatedString = random.ints(leftLimit, rightLimit + 1)
228                 .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)).limit(targetStringLength)
229                 .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
230         return generatedString;
231     }
232
233     /**
234      *
235      * @param key environment var
236      * @param defValue default value if no env var found
237      * @return
238      */
239     public static String getProperty(final String key, final String defValue) {
240         String value = defValue;
241         //try to read env var
242         boolean found = false;
243         if (isEnvExpression(key)) {
244
245             LOG.debug("try to find env var(s) for {}", key);
246             final Matcher matcher = pattern.matcher(key);
247             String tmp = new String(key);
248             while (matcher.find() && matcher.groupCount() > 0) {
249                 final String mkey = matcher.group(1);
250                 if (mkey != null) {
251                     try {
252                         LOG.debug("match found for v={} and env key={}", key, mkey);
253                         String envvar = mkey.substring(2, mkey.length() - 1);
254                         String env = System.getenv(envvar);
255                         tmp = tmp.replace(mkey, env == null ? "" : env);
256                         if (env != null && !env.isEmpty()) {
257                             found = true;
258                         }
259                     } catch (SecurityException e) {
260                         LOG.warn("unable to read env {}: {}", key, e);
261                     }
262                 }
263             }
264             if (found) {
265                 value = tmp;
266             }
267         }
268         return value;
269     }
270
271     public static boolean getPropertyBoolean(String key, boolean defaultValue) {
272         final String value = getProperty(key, String.valueOf(defaultValue));
273         return value.equals("true");
274     }
275
276     public static Config load(String filename) throws IOException, InvalidConfigurationException {
277         CustomObjectMapper mapper = new CustomObjectMapper();
278         File file = new File(filename);
279         if (!file.exists()) {
280             throw new FileNotFoundException();
281         }
282         String content = String.join("", Files.readAllLines(file.toPath()));
283         Config cfg = mapper.readValue(content, Config.class);
284         cfg.handleEnvironmentVars();
285         cfg.handleDefaultValues();
286         cfg.validate();
287         return cfg;
288     }
289
290
291     @JsonIgnore
292     private void validate() throws InvalidConfigurationException {
293         //verify that algorithm is supported
294         if (!VALID_ALGORITHMS.contains(this.algorithm)) {
295             throw new InvalidConfigurationException(String.format("Algorithm '%s' is not supported ", this.algorithm));
296         }
297         //verify that set values are matching the algorithm
298         //if hs256 check if secret is set
299         if (this.algorithm.startsWith("HS")) {
300             if (this.tokenSecret == null || this.tokenSecret.isBlank()) {
301                 throw new InvalidConfigurationException(
302                         String.format("There is no secret set for algorithm '%s'", this.algorithm));
303             }
304         }
305         //if rs256 or rs512 check if secret(private key) and pubkey are set
306         if (this.algorithm.startsWith("RS")) {
307             if (this.tokenSecret == null || this.tokenSecret.isBlank()) {
308                 throw new InvalidConfigurationException(
309                         String.format("There is no secret set for algorithm '%s'", this.algorithm));
310             }
311             if (this.tokenPubKey == null || this.tokenPubKey.isBlank()) {
312                 throw new InvalidConfigurationException(
313                         String.format("There is no public key for algorithm '%s'", this.algorithm));
314             }
315         }
316         //if client rs256 or client rs512 check if pubkey are set
317         if (this.algorithm.startsWith("Client")) {
318             if (this.tokenPubKey == null || this.tokenPubKey.isBlank()) {
319                 throw new InvalidConfigurationException(
320                         String.format("There is no public key for algorithm '%s'", this.algorithm));
321             }
322         }
323     }
324
325     @JsonIgnore
326     public boolean doSupportOdlUsers() {
327         return "true".equals(this.supportOdlUsers);
328     }
329
330
331     public static Config getInstance() throws IOException, InvalidConfigurationException {
332         return getInstance(DEFAULT_CONFIGFILENAME);
333     }
334
335     public static Config getInstance(String filename) throws IOException, InvalidConfigurationException {
336         if (_instance == null) {
337             _instance = load(filename);
338         }
339         return _instance;
340     }
341
342     public boolean loginActive() {
343         return VALID_ALGORITHMS_FOR_INTERNAL_LOGIN.contains(this.algorithm);
344     }
345
346
347 }