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;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Optional;
29  import java.util.concurrent.TimeUnit;
30  import javax.ws.rs.core.HttpHeaders;
31  import javax.ws.rs.core.MediaType;
32  import org.apache.commons.lang3.tuple.Triple;
33  import org.apache.cxf.jaxrs.client.WebClient;
34  import org.apache.http.Consts;
35  import org.apache.http.NameValuePair;
36  import org.apache.http.client.entity.UrlEncodedFormEntity;
37  import org.apache.http.client.methods.CloseableHttpResponse;
38  import org.apache.http.client.methods.HttpPost;
39  import org.apache.http.client.protocol.HttpClientContext;
40  import org.apache.http.impl.client.CloseableHttpClient;
41  import org.apache.http.message.BasicNameValuePair;
42  import org.apache.syncope.client.lib.SyncopeClient;
43  import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
44  import org.apache.syncope.common.rest.api.service.ClientAppService;
45  import org.apache.syncope.common.rest.api.service.OIDCC4UIProviderService;
46  import org.apache.syncope.common.rest.api.service.PolicyService;
47  import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService;
48  import org.apache.syncope.common.rest.api.service.SAML2SP4UIIdPService;
49  import org.apache.syncope.common.rest.api.service.SRARouteService;
50  import org.apache.syncope.common.rest.api.service.UserService;
51  import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
52  import org.apache.syncope.fit.sra.AbstractSRAITCase;
53  import org.apereo.cas.oidc.OidcConstants;
54  import org.jsoup.Connection;
55  import org.jsoup.Jsoup;
56  import org.jsoup.nodes.FormElement;
57  import org.junit.jupiter.api.BeforeAll;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  public abstract class AbstractITCase {
62  
63      protected static final Logger LOG = LoggerFactory.getLogger(AbstractSRAITCase.class);
64  
65      protected static final String ADMIN_UNAME = "admin";
66  
67      protected static final String ADMIN_PWD = "password";
68  
69      private static final String ANONYMOUS_USER = "anonymous";
70  
71      private static final String ANONYMOUS_KEY = "anonymousKey";
72  
73      protected static final String CORE_ADDRESS = "https://localhost:9443/syncope/rest";
74  
75      protected static final String CONSOLE_ADDRESS = "https://localhost:9443/syncope-console/";
76  
77      protected static final String ENDUSER_ADDRESS = "https://localhost:9443/syncope-enduser/";
78  
79      protected static final String WA_ADDRESS = "https://localhost:9443/syncope-wa";
80  
81      protected static final String EN_LANGUAGE = "en-US,en;q=0.5";
82  
83      protected static SyncopeClientFactoryBean CLIENT_FACTORY;
84  
85      protected static SyncopeClient ADMIN_CLIENT;
86  
87      protected static UserService USER_SERVICE;
88  
89      protected static PolicyService POLICY_SERVICE;
90  
91      protected static ClientAppService CLIENT_APP_SERVICE;
92  
93      protected static WAConfigService WA_CONFIG_SERVICE;
94  
95      protected static SRARouteService SRA_ROUTE_SERVICE;
96  
97      protected static SAML2SP4UIIdPService SAML2SP4UI_IDP_SERVICE;
98  
99      protected static OIDCC4UIProviderService OIDCC4UI_PROVIDER_SERVICE;
100 
101     @BeforeAll
102     public static void restSetup() {
103         CLIENT_FACTORY = new SyncopeClientFactoryBean().setAddress(CORE_ADDRESS);
104         ADMIN_CLIENT = CLIENT_FACTORY.create(ADMIN_UNAME, ADMIN_PWD);
105 
106         USER_SERVICE = ADMIN_CLIENT.getService(UserService.class);
107         POLICY_SERVICE = ADMIN_CLIENT.getService(PolicyService.class);
108         CLIENT_APP_SERVICE = ADMIN_CLIENT.getService(ClientAppService.class);
109         WA_CONFIG_SERVICE = ADMIN_CLIENT.getService(WAConfigService.class);
110         SRA_ROUTE_SERVICE = ADMIN_CLIENT.getService(SRARouteService.class);
111         SAML2SP4UI_IDP_SERVICE = ADMIN_CLIENT.getService(SAML2SP4UIIdPService.class);
112         OIDCC4UI_PROVIDER_SERVICE = ADMIN_CLIENT.getService(OIDCC4UIProviderService.class);
113     }
114 
115     @BeforeAll
116     public static void waitForWARefresh() {
117         SAML2IdPEntityService samlIdPEntityService = ADMIN_CLIENT.getService(SAML2IdPEntityService.class);
118 
119         await().atMost(120, TimeUnit.SECONDS).pollInterval(20, TimeUnit.SECONDS).until(() -> {
120             boolean refreshed = false;
121             try {
122                 String metadata = WebClient.create(
123                         WA_ADDRESS + "/idp/metadata").get().readEntity(String.class);
124                 if (metadata.contains("localhost:8080")) {
125                     throw new IllegalStateException();
126                 }
127                 metadata = WebClient.create(
128                         WA_ADDRESS + "/oidc/" + OidcConstants.WELL_KNOWN_OPENID_CONFIGURATION_URL).
129                         get().readEntity(String.class);
130                 if (metadata.contains("localhost:8080")) {
131                     throw new IllegalStateException();
132                 }
133                 metadata = WebClient.create(
134                         WA_ADDRESS + "/actuator/registeredServices", ANONYMOUS_USER, ANONYMOUS_KEY, null).
135                         get().readEntity(String.class);
136                 if (metadata.contains("localhost:8080/syncope-wa")) {
137                     throw new IllegalStateException();
138                 }
139                 metadata = WebClient.create(
140                         WA_ADDRESS + "/actuator/authenticationHandlers", ANONYMOUS_USER, ANONYMOUS_KEY, null).
141                         get().readEntity(String.class);
142                 if (!metadata.contains("DefaultLDAPAuthModule")) {
143                     throw new IllegalStateException();
144                 }
145 
146                 samlIdPEntityService.get(SAML2IdPEntityService.DEFAULT_OWNER);
147                 refreshed = true;
148             } catch (IllegalStateException e) {
149                 WebClient.create(
150                         WA_ADDRESS + "/actuator/refresh", ANONYMOUS_USER, ANONYMOUS_KEY, null).
151                         post(null);
152             } catch (Exception e) {
153                 // ignore
154             }
155             return refreshed;
156         });
157     }
158 
159     protected static Triple<String, String, String> parseSAMLRequestForm(final String body) {
160         FormElement form = (FormElement) Jsoup.parse(body).body().getElementsByTag("form").first();
161         assertNotNull(form);
162 
163         String action = form.attr("action");
164         assertNotNull(action);
165 
166         Optional<String> relayState = form.formData().stream().
167                 filter(keyval -> "RelayState".equals(keyval.key())).
168                 map(Connection.KeyVal::value).
169                 findFirst();
170         assertTrue(relayState.isPresent());
171 
172         Optional<String> samlRequest = form.formData().stream().
173                 filter(keyval -> "SAMLRequest".equals(keyval.key())).
174                 map(Connection.KeyVal::value).
175                 findFirst();
176         assertTrue(samlRequest.isPresent());
177 
178         return Triple.of(action, relayState.get(), samlRequest.get());
179     }
180 
181     protected static Triple<String, String, String> parseSAMLResponseForm(final String body) {
182         FormElement form = (FormElement) Jsoup.parse(body).body().getElementsByTag("form").first();
183         assertNotNull(form);
184 
185         String action = form.attr("action");
186         assertNotNull(action);
187 
188         Optional<String> relayState = form.formData().stream().
189                 filter(keyval -> "RelayState".equals(keyval.key())).
190                 map(Connection.KeyVal::value).
191                 findFirst();
192         assertTrue(relayState.isPresent());
193 
194         Optional<String> samlResponse = form.formData().stream().
195                 filter(keyval -> "SAMLResponse".equals(keyval.key())).
196                 map(Connection.KeyVal::value).
197                 findFirst();
198         assertTrue(samlResponse.isPresent());
199 
200         return Triple.of(action, relayState.get(), samlResponse.get());
201     }
202 
203     protected static String extractWAExecution(final String body) {
204         FormElement form = (FormElement) Jsoup.parse(body).body().getElementsByTag("form").first();
205         assertNotNull(form);
206 
207         Optional<String> execution = form.formData().stream().
208                 filter(keyval -> "execution".equals(keyval.key())).
209                 map(Connection.KeyVal::value).
210                 findFirst();
211         assertTrue(execution.isPresent());
212 
213         return execution.get();
214     }
215 
216     protected static CloseableHttpResponse authenticateToWA(
217             final String username,
218             final String password,
219             final String body,
220             final CloseableHttpClient httpclient,
221             final HttpClientContext context)
222             throws IOException {
223 
224         List<NameValuePair> form = new ArrayList<>();
225         form.add(new BasicNameValuePair("_eventId", "submit"));
226         form.add(new BasicNameValuePair("execution", extractWAExecution(body)));
227         form.add(new BasicNameValuePair("username", username));
228         form.add(new BasicNameValuePair("password", password));
229         form.add(new BasicNameValuePair("geolocation", ""));
230 
231         HttpPost post = new HttpPost(WA_ADDRESS + "/login");
232         post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
233         post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
234         post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8));
235         return httpclient.execute(post, context);
236     }
237 }