View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.fit.core;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assertions.fail;
29  import static org.junit.jupiter.api.Assumptions.assumeTrue;
30  
31  import java.security.AccessControlException;
32  import java.util.ArrayList;
33  import java.util.Date;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.UUID;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.atomic.AtomicReference;
39  import java.util.function.Function;
40  import javax.ws.rs.core.GenericType;
41  import javax.ws.rs.core.Response;
42  import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
43  import org.apache.syncope.common.keymaster.client.api.KeymasterException;
44  import org.apache.syncope.common.keymaster.client.api.model.Domain;
45  import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
46  import org.apache.syncope.common.keymaster.client.self.SelfKeymasterDomainOps;
47  import org.apache.syncope.common.lib.SyncopeConstants;
48  import org.apache.syncope.common.lib.request.UserCR;
49  import org.apache.syncope.common.lib.to.PagedResult;
50  import org.apache.syncope.common.lib.to.ProvisioningResult;
51  import org.apache.syncope.common.lib.to.UserTO;
52  import org.apache.syncope.common.lib.types.CipherAlgorithm;
53  import org.apache.syncope.common.rest.api.beans.AnyQuery;
54  import org.apache.syncope.common.rest.api.service.UserService;
55  import org.apache.syncope.core.spring.security.Encryptor;
56  import org.apache.syncope.fit.AbstractITCase;
57  import org.junit.jupiter.api.Test;
58  
59  public class KeymasterITCase extends AbstractITCase {
60  
61      @Test
62      public void confParamList() {
63          Map<String, Object> confParams = confParamOps.list(SyncopeConstants.MASTER_DOMAIN);
64          assertNotNull(confParams);
65          assertFalse(confParams.isEmpty());
66      }
67  
68      @Test
69      public void confParamGet() {
70          String stringValue = confParamOps.get(
71                  SyncopeConstants.MASTER_DOMAIN, "password.cipher.algorithm", null, String.class);
72          assertNotNull(stringValue);
73          assertEquals("SHA1", stringValue);
74  
75          Long longValue = confParamOps.get(
76                  SyncopeConstants.MASTER_DOMAIN, "jwt.lifetime.minutes", null, Long.class);
77          assertNotNull(longValue);
78          assertEquals(120L, longValue.longValue());
79  
80          Boolean booleanValue = confParamOps.get(
81                  SyncopeConstants.MASTER_DOMAIN, "return.password.value", null, Boolean.class);
82          assertNotNull(booleanValue);
83          assertEquals(false, booleanValue);
84  
85          List<String> stringValues =
86                  List.of(confParamOps.get(
87                          SyncopeConstants.MASTER_DOMAIN, "authentication.attributes", null, String[].class));
88          assertNotNull(stringValues);
89          List<String> actualStringValues = new ArrayList<>();
90          actualStringValues.add("username");
91          actualStringValues.add("userId");
92          assertEquals(actualStringValues, stringValues);
93      }
94  
95      @Test
96      public void confParamSetGetRemove() {
97          String key = UUID.randomUUID().toString();
98  
99          String stringValue = "stringValue";
100         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, stringValue);
101         String actualStringValue = confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, String.class);
102         assertEquals(stringValue, actualStringValue);
103 
104         Long longValue = 1L;
105         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, longValue);
106         Long actualLongValue = confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, Long.class);
107         assertEquals(longValue, actualLongValue);
108 
109         Double doubleValue = 2.0;
110         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, doubleValue);
111         Double actualDoubleValue = confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, Double.class);
112         assertEquals(doubleValue, actualDoubleValue);
113 
114         Date dateValue = new Date();
115         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, dateValue);
116         Date actualDateValue = confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, Date.class);
117         assertEquals(dateValue, actualDateValue);
118 
119         Boolean booleanValue = true;
120         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, booleanValue);
121         Boolean actualBooleanValue = confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, Boolean.class);
122         assertEquals(booleanValue, actualBooleanValue);
123 
124         List<String> stringValues = new ArrayList<>();
125         stringValues.add("stringValue1");
126         stringValues.add("stringValue2");
127         confParamOps.set(SyncopeConstants.MASTER_DOMAIN, key, stringValues);
128         List<String> actualStringValues =
129                 List.of(confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, String[].class));
130         assertEquals(stringValues, actualStringValues);
131 
132         confParamOps.remove(SyncopeConstants.MASTER_DOMAIN, key);
133         assertNull(confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, null, String.class));
134         assertEquals(
135                 "defaultValue",
136                 confParamOps.get(SyncopeConstants.MASTER_DOMAIN, key, "defaultValue", String.class));
137     }
138 
139     @Test
140     public void serviceList() {
141         List<NetworkService> services = serviceOps.list(NetworkService.Type.CORE);
142         assertFalse(services.isEmpty());
143         assertEquals(1, services.size());
144 
145         services = serviceOps.list(NetworkService.Type.SRA);
146         assertTrue(services.isEmpty());
147 
148         services = serviceOps.list(NetworkService.Type.WA);
149         assertTrue(services.isEmpty());
150     }
151 
152     private List<NetworkService> findNetworkServices(
153             final NetworkService.Type type,
154             final Function<List<NetworkService>, Boolean> check,
155             final int maxWaitSeconds) {
156 
157         AtomicReference<List<NetworkService>> holder = new AtomicReference<>();
158         await().atMost(maxWaitSeconds, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
159             try {
160                 holder.set(serviceOps.list(type));
161                 return !check.apply(holder.get());
162             } catch (Exception e) {
163                 return false;
164             }
165         });
166         return holder.get();
167     }
168 
169     @Test
170     public void serviceRun() {
171         List<NetworkService> list = serviceOps.list(NetworkService.Type.SRA);
172         assertTrue(list.isEmpty());
173 
174         NetworkService sra1 = new NetworkService();
175         sra1.setType(NetworkService.Type.SRA);
176         sra1.setAddress("http://localhost:9080/syncope-sra");
177         serviceOps.register(sra1);
178 
179         list = findNetworkServices(NetworkService.Type.SRA, List::isEmpty, 30);
180         assertFalse(list.isEmpty());
181         assertEquals(1, list.size());
182         assertEquals(sra1, list.get(0));
183 
184         assertEquals(sra1, serviceOps.get(NetworkService.Type.SRA));
185 
186         NetworkService sra2 = new NetworkService();
187         sra2.setType(NetworkService.Type.SRA);
188         sra2.setAddress("http://localhost:9080/syncope-sra");
189         assertEquals(sra1, sra2);
190         serviceOps.register(sra2);
191 
192         list = findNetworkServices(NetworkService.Type.SRA, List::isEmpty, 30);
193         assertFalse(list.isEmpty());
194         assertEquals(1, list.size());
195         assertEquals(sra1, list.get(0));
196 
197         assertEquals(sra1, serviceOps.get(NetworkService.Type.SRA));
198 
199         serviceOps.unregister(sra1);
200         list = findNetworkServices(NetworkService.Type.SRA, l -> !l.isEmpty(), 30);
201         assertTrue(list.isEmpty());
202 
203         try {
204             serviceOps.get(NetworkService.Type.SRA);
205             fail();
206         } catch (KeymasterException e) {
207             assertNotNull(e);
208         }
209     }
210 
211     @Test
212     public void domainCRUD() throws Exception {
213         List<Domain> initial = domainOps.list();
214         assertNotNull(initial);
215         assumeTrue(initial.stream().anyMatch(domain -> "Two".equals(domain.getKey())));
216 
217         // 1. create new domain
218         String key = UUID.randomUUID().toString();
219 
220         domainOps.create(new Domain.Builder(key).
221                 jdbcDriver("org.h2.Driver").
222                 jdbcURL("jdbc:h2:mem:syncopetest" + key + ";DB_CLOSE_DELAY=-1").
223                 dbUsername("sa").
224                 dbPassword("").
225                 databasePlatform("org.apache.openjpa.jdbc.sql.H2Dictionary").
226                 transactionIsolation(Domain.TransactionIsolation.TRANSACTION_READ_UNCOMMITTED).
227                 adminPassword(Encryptor.getInstance().encode("password", CipherAlgorithm.BCRYPT)).
228                 adminCipherAlgorithm(CipherAlgorithm.BCRYPT).
229                 build());
230 
231         Domain domain = domainOps.read(key);
232         assertEquals(Domain.TransactionIsolation.TRANSACTION_READ_UNCOMMITTED, domain.getTransactionIsolation());
233         assertEquals(CipherAlgorithm.BCRYPT, domain.getAdminCipherAlgorithm());
234         assertEquals(10, domain.getPoolMaxActive());
235         assertEquals(2, domain.getPoolMinIdle());
236 
237         assertEquals(domain, domainOps.read(key));
238 
239         // 2. update domain
240         domainOps.adjustPoolSize(key, 100, 23);
241 
242         domain = domainOps.read(key);
243         assertEquals(100, domain.getPoolMaxActive());
244         assertEquals(23, domain.getPoolMinIdle());
245 
246         // temporarily finish test case at this point in case Zookeeper
247         // is used: in such a case, in fact, errors are found in the logs
248         // at this point as follows:
249         // org.springframework.beans.factory.BeanCreationException: Error creating bean
250         // with name
251         // 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration':
252         // Initialization of bean failed; nested exception is
253         // org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named
254         // 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry'
255         // available
256         // the same test, execute alone, works fine with Zookeeper, so it musy be something
257         // set or left unclean from previous tests
258         assumeTrue(domainOps instanceof SelfKeymasterDomainOps);
259 
260         // 3. work with new domain - create user
261         CLIENT_FACTORY = new SyncopeClientFactoryBean().setAddress(ADDRESS).setDomain(key);
262         ADMIN_CLIENT = CLIENT_FACTORY.create(ADMIN_UNAME, "password");
263 
264         USER_SERVICE = ADMIN_CLIENT.getService(UserService.class);
265 
266         PagedResult<UserTO> users = USER_SERVICE.search(
267                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).page(1).size(1).build());
268         assertNotNull(users);
269         assertTrue(users.getResult().isEmpty());
270         assertEquals(0, users.getTotalCount());
271 
272         Response response = USER_SERVICE.create(
273                 new UserCR.Builder(SyncopeConstants.ROOT_REALM, "monteverdi").
274                         password("password123").
275                         plainAttr(attr("email", "monteverdi@syncope.apache.org")).
276                         build());
277         assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
278 
279         UserTO user = response.readEntity(new GenericType<ProvisioningResult<UserTO>>() {
280         }).getEntity();
281         assertNotNull(user);
282         assertEquals("monteverdi", user.getUsername());
283 
284         if (IS_EXT_SEARCH_ENABLED) {
285             try {
286                 Thread.sleep(2000);
287             } catch (InterruptedException ex) {
288                 // ignore
289             }
290         }
291 
292         users = USER_SERVICE.search(
293                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).page(1).size(1).build());
294         assertNotNull(users);
295         assertFalse(users.getResult().isEmpty());
296         assertEquals(1, users.getTotalCount());
297 
298         // 4. delete domain
299         domainOps.delete(key);
300 
301         List<Domain> list = domainOps.list();
302         assertEquals(initial, list);
303     }
304 
305     @Test
306     public void domainCreateMaster() {
307         assertThrows(
308                 KeymasterException.class,
309                 () -> domainOps.create(new Domain.Builder(SyncopeConstants.MASTER_DOMAIN).build()));
310     }
311 
312     @Test
313     public void domainCreateDuplicateKey() {
314         assertThrows(KeymasterException.class, () -> domainOps.create(new Domain.Builder("Two").build()));
315     }
316 
317     @Test
318     public void domainUpdateAdminPassword() throws Exception {
319         List<Domain> initial = domainOps.list();
320         assertNotNull(initial);
321         assumeTrue(initial.stream().anyMatch(domain -> "Two".equals(domain.getKey())));
322 
323         Domain two = domainOps.read("Two");
324         assertNotNull(two);
325 
326         String origPasswowrd = two.getAdminPassword();
327         CipherAlgorithm origCipherAlgo = two.getAdminCipherAlgorithm();
328 
329         try {
330             // 1. change admin pwd for domain Two
331             domainOps.changeAdminPassword(
332                     two.getKey(),
333                     Encryptor.getInstance().encode("password3", CipherAlgorithm.AES),
334                     CipherAlgorithm.AES);
335 
336             // 2. attempt to access with old pwd -> fail
337             try {
338                 new SyncopeClientFactoryBean().
339                         setAddress(ADDRESS).setDomain(two.getKey()).setContentType(CLIENT_FACTORY.getContentType()).
340                         create(ADMIN_UNAME, "password2").self();
341             } catch (AccessControlException e) {
342                 assertNotNull(e);
343             }
344 
345             // 3. access with new pwd -> succeed
346             new SyncopeClientFactoryBean().
347                     setAddress(ADDRESS).setDomain(two.getKey()).setContentType(CLIENT_FACTORY.getContentType()).
348                     create(ADMIN_UNAME, "password3").self();
349         } finally {
350             domainOps.changeAdminPassword(two.getKey(), origPasswowrd, origCipherAlgo);
351         }
352     }
353 }