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.wa.starter;
20  
21  import com.fasterxml.jackson.databind.json.JsonMapper;
22  import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
23  import jakarta.ws.rs.NotFoundException;
24  import jakarta.ws.rs.core.Response;
25  import java.time.LocalDateTime;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
32  import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
33  import org.apache.syncope.common.keymaster.client.api.ServiceOps;
34  import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
35  import org.apache.syncope.common.lib.Attr;
36  import org.apache.syncope.common.lib.to.AttrRepoTO;
37  import org.apache.syncope.common.lib.to.AuditConfTO;
38  import org.apache.syncope.common.lib.to.AuditEventTO;
39  import org.apache.syncope.common.lib.to.AuthModuleTO;
40  import org.apache.syncope.common.lib.to.PagedResult;
41  import org.apache.syncope.common.lib.to.PasswordManagementTO;
42  import org.apache.syncope.common.lib.types.ClientAppType;
43  import org.apache.syncope.common.lib.types.OpEvent;
44  import org.apache.syncope.common.lib.wa.GoogleMfaAuthToken;
45  import org.apache.syncope.common.lib.wa.ImpersonationAccount;
46  import org.apache.syncope.common.lib.wa.WAClientApp;
47  import org.apache.syncope.common.rest.api.beans.AuditQuery;
48  import org.apache.syncope.common.rest.api.service.AttrRepoService;
49  import org.apache.syncope.common.rest.api.service.AuditService;
50  import org.apache.syncope.common.rest.api.service.AuthModuleService;
51  import org.apache.syncope.common.rest.api.service.PasswordManagementService;
52  import org.apache.syncope.common.rest.api.service.wa.GoogleMfaAuthTokenService;
53  import org.apache.syncope.common.rest.api.service.wa.ImpersonationService;
54  import org.apache.syncope.common.rest.api.service.wa.WAClientAppService;
55  import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
56  import org.springframework.beans.factory.annotation.Autowired;
57  import org.springframework.context.ApplicationListener;
58  import org.springframework.context.event.ContextRefreshedEvent;
59  
60  public class SyncopeCoreTestingServer implements ApplicationListener<ContextRefreshedEvent> {
61  
62      private static final String ADDRESS = "http://localhost:9081/syncope/rest";
63  
64      public static final List<AuthModuleTO> AUTH_MODULES = new ArrayList<>();
65  
66      public static final List<AttrRepoTO> ATTR_REPOS = new ArrayList<>();
67  
68      public static final List<PasswordManagementTO> PASSWORD_MANAGEMENTS = new ArrayList<>();
69  
70      public static final List<WAClientApp> CLIENT_APPS = new ArrayList<>();
71  
72      public static final List<Attr> CONFIG = new ArrayList<>();
73  
74      protected static class StubAuthModuleService implements AuthModuleService {
75  
76          @Override
77          public AuthModuleTO read(final String key) {
78              return AUTH_MODULES.stream().filter(m -> Objects.equals(key, m.getKey())).
79                      findFirst().orElseThrow(() -> new NotFoundException("AuthModule with key " + key));
80          }
81  
82          @Override
83          public List<AuthModuleTO> list() {
84              return AUTH_MODULES;
85          }
86  
87          @Override
88          public Response create(final AuthModuleTO authModuleTO) {
89              AUTH_MODULES.add(authModuleTO);
90              return Response.created(null).build();
91          }
92  
93          @Override
94          public void update(final AuthModuleTO authModuleTO) {
95              delete(authModuleTO.getKey());
96              create(authModuleTO);
97          }
98  
99          @Override
100         public void delete(final String key) {
101             AUTH_MODULES.removeIf(m -> Objects.equals(key, m.getKey()));
102         }
103     }
104 
105     protected static class StubAttrRepoService implements AttrRepoService {
106 
107         @Override
108         public AttrRepoTO read(final String key) {
109             return ATTR_REPOS.stream().filter(m -> Objects.equals(key, m.getKey())).
110                     findFirst().orElseThrow(() -> new NotFoundException("Attr Repo with key " + key));
111         }
112 
113         @Override
114         public List<AttrRepoTO> list() {
115             return ATTR_REPOS;
116         }
117 
118         @Override
119         public Response create(final AttrRepoTO attrRepoTO) {
120             ATTR_REPOS.add(attrRepoTO);
121             return Response.created(null).build();
122         }
123 
124         @Override
125         public void update(final AttrRepoTO attrRepoTO) {
126             delete(attrRepoTO.getKey());
127             create(attrRepoTO);
128         }
129 
130         @Override
131         public void delete(final String key) {
132             ATTR_REPOS.removeIf(m -> Objects.equals(key, m.getKey()));
133         }
134     }
135 
136     protected static class StubPasswordManagementService implements PasswordManagementService {
137 
138         @Override
139         public PasswordManagementTO read(final String key) {
140             return PASSWORD_MANAGEMENTS.stream().filter(pm -> Objects.equals(key, pm.getKey())).
141                     findFirst().orElseThrow(() -> new NotFoundException("Password Management with key " + key));
142         }
143 
144         @Override
145         public List<PasswordManagementTO> list() {
146             return PASSWORD_MANAGEMENTS;
147         }
148 
149         @Override
150         public Response create(final PasswordManagementTO passwordManagementTO) {
151             PASSWORD_MANAGEMENTS.add(passwordManagementTO);
152             return Response.created(null).build();
153         }
154 
155         @Override
156         public void update(final PasswordManagementTO passwordManagementTO) {
157             delete(passwordManagementTO.getKey());
158             create(passwordManagementTO);
159         }
160 
161         @Override
162         public void delete(final String key) {
163             PASSWORD_MANAGEMENTS.removeIf(pm -> Objects.equals(key, pm.getKey()));
164         }
165     }
166 
167     protected static class StubWAClientAppService implements WAClientAppService {
168 
169         @Override
170         public List<WAClientApp> list() {
171             return CLIENT_APPS;
172         }
173 
174         @Override
175         public WAClientApp read(final Long clientAppId, final ClientAppType type) {
176             return CLIENT_APPS.stream().
177                     filter(app -> Objects.equals(clientAppId, app.getClientAppTO().getClientAppId())).
178                     findFirst().orElseThrow(() -> new NotFoundException("ClientApp with clientId " + clientAppId));
179         }
180 
181         @Override
182         public WAClientApp read(final String name, final ClientAppType type) {
183             return CLIENT_APPS.stream().filter(app -> Objects.equals(name, app.getClientAppTO().getName())).
184                     findFirst().orElseThrow(() -> new NotFoundException("ClientApp with name " + name));
185         }
186     }
187 
188     protected static class StubWAConfigService implements WAConfigService {
189 
190         @Override
191         public List<Attr> list() {
192             return CONFIG;
193         }
194 
195         @Override
196         public Attr get(final String schema) {
197             return CONFIG.stream().filter(c -> Objects.equals(schema, c.getSchema())).
198                     findFirst().orElseThrow(() -> new NotFoundException("Config with schema " + schema));
199         }
200 
201         @Override
202         public void set(final Attr value) {
203             delete(value.getSchema());
204             CONFIG.add(value);
205         }
206 
207         @Override
208         public void delete(final String schema) {
209             CONFIG.removeIf(c -> Objects.equals(schema, c.getSchema()));
210         }
211 
212         @Override
213         public void pushToWA(final PushSubject subject, final List<String> services) {
214             // nothing to do
215         }
216     }
217 
218     protected static class StubImpersonationService implements ImpersonationService {
219 
220         private final Map<String, List<ImpersonationAccount>> accounts = new HashMap<>();
221 
222         @Override
223         public List<ImpersonationAccount> read(final String owner) {
224             return accounts.containsKey(owner) ? accounts.get(owner) : List.of();
225         }
226 
227         @Override
228         public void create(final String owner, final ImpersonationAccount account) {
229             try {
230                 if (accounts.containsKey(owner) && accounts.get(owner).stream().
231                         noneMatch(acct -> acct.getImpersonated().equalsIgnoreCase(account.getImpersonated()))) {
232 
233                     accounts.get(owner).add(account);
234                 } else {
235                     List<ImpersonationAccount> list = new ArrayList<>();
236                     list.add(account);
237                     accounts.put(owner, list);
238                 }
239             } catch (final Exception e) {
240                 throw new IllegalStateException(e);
241             }
242         }
243 
244         @Override
245         public void delete(final String owner, final String impersonated) {
246             if (accounts.containsKey(owner)) {
247                 accounts.get(owner).removeIf(acct -> acct.getImpersonated().equalsIgnoreCase(impersonated));
248             }
249         }
250     }
251 
252     protected static class StubGoogleMfaAuthTokenService implements GoogleMfaAuthTokenService {
253 
254         private final Map<String, GoogleMfaAuthToken> tokens = new HashMap<>();
255 
256         @Override
257         public void delete(final LocalDateTime expirationDate) {
258             if (expirationDate == null) {
259                 tokens.clear();
260             } else {
261                 tokens.entrySet().removeIf(token -> token.getValue().getIssueDate().compareTo(expirationDate) >= 0);
262             }
263         }
264 
265         @Override
266         public void delete(final String owner, final int otp) {
267             tokens.entrySet().
268                     removeIf(e -> e.getValue().getOtp() == otp && e.getKey().equalsIgnoreCase(owner));
269         }
270 
271         @Override
272         public void delete(final String owner) {
273             tokens.entrySet().removeIf(e -> e.getKey().equalsIgnoreCase(owner));
274         }
275 
276         @Override
277         public void delete(final int otp) {
278             tokens.entrySet().removeIf(to -> to.getValue().getOtp() == otp);
279         }
280 
281         @Override
282         public void store(final String owner, final GoogleMfaAuthToken tokenTO) {
283             tokens.put(owner, tokenTO);
284         }
285 
286         @Override
287         public GoogleMfaAuthToken read(final String owner, final int otp) {
288             return tokens.entrySet().stream().
289                     filter(to -> to.getValue().getOtp() == otp && to.getKey().equalsIgnoreCase(owner)).
290                     findFirst().get().getValue();
291         }
292 
293         @Override
294         public PagedResult<GoogleMfaAuthToken> read(final String user) {
295             PagedResult<GoogleMfaAuthToken> result = new PagedResult<>();
296             result.getResult().addAll(tokens.entrySet().stream().
297                     filter(to -> to.getKey().equalsIgnoreCase(user)).
298                     map(Map.Entry::getValue).
299                     toList());
300             result.setSize(result.getResult().size());
301             result.setTotalCount(result.getSize());
302             return result;
303         }
304 
305         @Override
306         public PagedResult<GoogleMfaAuthToken> list() {
307             PagedResult<GoogleMfaAuthToken> result = new PagedResult<>();
308             result.setSize(tokens.size());
309             result.setTotalCount(tokens.size());
310             result.getResult().addAll(tokens.values());
311             return result;
312         }
313     }
314 
315     protected static class StubAuditService implements AuditService {
316 
317         @Override
318         public List<AuditConfTO> confs() {
319             return List.of();
320         }
321 
322         @Override
323         public AuditConfTO getConf(final String key) {
324             throw new NotFoundException();
325         }
326 
327         @Override
328         public void setConf(final AuditConfTO auditTO) {
329             // nothing to do
330         }
331 
332         @Override
333         public void deleteConf(final String key) {
334             // nothing to do
335         }
336 
337         @Override
338         public List<OpEvent> events() {
339             return List.of();
340         }
341 
342         @Override
343         public PagedResult<AuditEventTO> search(final AuditQuery auditQuery) {
344             return new PagedResult<>();
345         }
346 
347         @Override
348         public void create(final AuditEventTO auditEvent) {
349             // nothing to do
350         }
351     }
352 
353     @Autowired
354     private ServiceOps serviceOps;
355 
356     @Override
357     public void onApplicationEvent(final ContextRefreshedEvent event) {
358         synchronized (ADDRESS) {
359             if (serviceOps.list(NetworkService.Type.CORE).isEmpty()) {
360                 // 1. start (mocked) Core as embedded CXF
361                 JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
362                 sf.setAddress(ADDRESS);
363                 sf.setResourceClasses(
364                         AuditService.class,
365                         AuthModuleService.class,
366                         AttrRepoService.class,
367                         PasswordManagementService.class,
368                         WAClientAppService.class,
369                         WAConfigService.class,
370                         GoogleMfaAuthTokenService.class,
371                         ImpersonationService.class);
372                 sf.setResourceProvider(
373                         AuditService.class,
374                         new SingletonResourceProvider(new StubAuditService(), true));
375                 sf.setResourceProvider(
376                         AuthModuleService.class,
377                         new SingletonResourceProvider(new StubAuthModuleService(), true));
378                 sf.setResourceProvider(
379                         AttrRepoService.class,
380                         new SingletonResourceProvider(new StubAttrRepoService(), true));
381                 sf.setResourceProvider(
382                         PasswordManagementService.class,
383                         new SingletonResourceProvider(new StubPasswordManagementService(), true));
384                 sf.setResourceProvider(
385                         WAClientAppService.class,
386                         new SingletonResourceProvider(new StubWAClientAppService(), true));
387                 sf.setResourceProvider(
388                         WAConfigService.class,
389                         new SingletonResourceProvider(new StubWAConfigService(), true));
390                 sf.setResourceProvider(
391                         GoogleMfaAuthTokenService.class,
392                         new SingletonResourceProvider(new StubGoogleMfaAuthTokenService(), true));
393                 sf.setResourceProvider(
394                         ImpersonationService.class,
395                         new SingletonResourceProvider(new StubImpersonationService(), true));
396                 sf.setProviders(List.of(new JacksonJsonProvider(JsonMapper.builder().findAndAddModules().build())));
397                 sf.create();
398 
399                 // 2. register Core in Keymaster
400                 NetworkService core = new NetworkService();
401                 core.setType(NetworkService.Type.CORE);
402                 core.setAddress(ADDRESS);
403                 serviceOps.register(core);
404             }
405         }
406     }
407 }