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.sra;
20  
21  import static org.awaitility.Awaitility.await;
22  import static org.hamcrest.MatcherAssert.assertThat;
23  import static org.hamcrest.Matchers.is;
24  import static org.hamcrest.Matchers.oneOf;
25  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
26  import static org.junit.jupiter.api.Assertions.assertEquals;
27  import static org.junit.jupiter.api.Assertions.assertNotNull;
28  import static org.junit.jupiter.api.Assertions.fail;
29  
30  import com.fasterxml.jackson.databind.JsonNode;
31  import com.fasterxml.jackson.databind.json.JsonMapper;
32  import com.fasterxml.jackson.databind.node.ArrayNode;
33  import com.fasterxml.jackson.databind.node.ObjectNode;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.net.ConnectException;
37  import java.net.InetSocketAddress;
38  import java.net.Socket;
39  import java.net.URI;
40  import java.util.Map;
41  import java.util.Properties;
42  import java.util.concurrent.TimeUnit;
43  import java.util.concurrent.TimeoutException;
44  import javax.ws.rs.core.HttpHeaders;
45  import javax.ws.rs.core.MediaType;
46  import javax.ws.rs.core.Response;
47  import org.apache.commons.lang3.StringUtils;
48  import org.apache.cxf.jaxrs.client.WebClient;
49  import org.apache.http.HttpStatus;
50  import org.apache.http.client.methods.CloseableHttpResponse;
51  import org.apache.http.util.EntityUtils;
52  import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
53  import org.apache.syncope.common.lib.policy.AuthPolicyTO;
54  import org.apache.syncope.common.lib.policy.DefaultAttrReleasePolicyConf;
55  import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
56  import org.apache.syncope.common.lib.to.SRARouteTO;
57  import org.apache.syncope.common.lib.types.PolicyType;
58  import org.apache.syncope.common.lib.types.SRARouteFilter;
59  import org.apache.syncope.common.lib.types.SRARouteFilterFactory;
60  import org.apache.syncope.common.lib.types.SRARoutePredicate;
61  import org.apache.syncope.common.lib.types.SRARoutePredicateFactory;
62  import org.apache.syncope.common.lib.types.SRARouteType;
63  import org.apache.syncope.common.rest.api.RESTHeaders;
64  import org.apache.syncope.fit.AbstractITCase;
65  import org.junit.jupiter.api.AfterAll;
66  import org.junit.jupiter.api.BeforeAll;
67  
68  public abstract class AbstractSRAITCase extends AbstractITCase {
69  
70      protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
71  
72      protected static final String SRA_ADDRESS = "http://127.0.0.1:8080";
73  
74      protected static final String QUERY_STRING =
75              "key1=value1&key2=value2&key2=value3&key3=an%20url%20encoded%20value%3A%20this%21";
76  
77      protected static final String LOGGED_OUT_HEADER = "X-LOGGED-OUT";
78  
79      private static Process SRA;
80  
81      @BeforeAll
82      public static void sraRouteSetup() {
83          SRA_ROUTE_SERVICE.list().forEach(route -> SRA_ROUTE_SERVICE.delete(route.getKey()));
84  
85          SRARouteTO publicRoute = new SRARouteTO();
86          publicRoute.setName("public");
87          publicRoute.setTarget(URI.create("http://localhost:80"));
88          publicRoute.setType(SRARouteType.PUBLIC);
89          publicRoute.setCsrf(false);
90          publicRoute.getPredicates().add(new SRARoutePredicate.Builder().
91                  factory(SRARoutePredicateFactory.PATH).args("/public/{segment}").build());
92          publicRoute.getFilters().add(new SRARouteFilter.Builder().
93                  factory(SRARouteFilterFactory.SET_PATH).args("/{segment}").build());
94  
95          Response response = SRA_ROUTE_SERVICE.create(publicRoute);
96          if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
97              fail("Could not create public SRA Route");
98          }
99  
100         SRARouteTO protectedRoute = new SRARouteTO();
101         protectedRoute.setName("protected");
102         protectedRoute.setTarget(URI.create("http://localhost:80"));
103         protectedRoute.setType(SRARouteType.PROTECTED);
104         protectedRoute.setCsrf(false);
105         protectedRoute.getPredicates().add(new SRARoutePredicate.Builder().
106                 factory(SRARoutePredicateFactory.PATH).args("/protected/{segment}").build());
107         protectedRoute.getFilters().add(new SRARouteFilter.Builder().
108                 factory(SRARouteFilterFactory.SET_PATH).args("/{segment}").build());
109 
110         response = SRA_ROUTE_SERVICE.create(protectedRoute);
111         if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
112             fail("Could not create protected SRA Route");
113         }
114 
115         SRARouteTO logoutRoute = new SRARouteTO();
116         logoutRoute.setName("logout");
117         logoutRoute.setTarget(URI.create("http://localhost:80"));
118         logoutRoute.setType(SRARouteType.PROTECTED);
119         logoutRoute.setLogout(true);
120         logoutRoute.getPredicates().add(new SRARoutePredicate.Builder().
121                 factory(SRARoutePredicateFactory.PATH).args("/protected/logout").build());
122         logoutRoute.setOrder(-1);
123 
124         response = SRA_ROUTE_SERVICE.create(logoutRoute);
125         if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
126             fail("Could not create logout SRA Route");
127         }
128 
129         SRARouteTO postLogout = new SRARouteTO();
130         postLogout.setName("postLogout");
131         postLogout.setTarget(URI.create("http://localhost:80"));
132         postLogout.setType(SRARouteType.PUBLIC);
133         postLogout.getPredicates().add(new SRARoutePredicate.Builder().
134                 factory(SRARoutePredicateFactory.PATH).args("/logout").build());
135         postLogout.getFilters().add(new SRARouteFilter.Builder().
136                 factory(SRARouteFilterFactory.SET_STATUS).args("204").build());
137         postLogout.getFilters().add(new SRARouteFilter.Builder().
138                 factory(SRARouteFilterFactory.SET_RESPONSE_HEADER).args(LOGGED_OUT_HEADER + ", true").build());
139         postLogout.setOrder(-10);
140 
141         response = SRA_ROUTE_SERVICE.create(postLogout);
142         if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
143             fail("Could not create logout SRA Route");
144         }
145     }
146 
147     protected static void doStartSRA(final String activeProfile)
148             throws IOException, InterruptedException, TimeoutException {
149 
150         Properties props = new Properties();
151         try (InputStream propStream = AbstractSRAITCase.class.getResourceAsStream("/test.properties")) {
152             props.load(propStream);
153         } catch (Exception e) {
154             fail("Could not load /test.properties", e);
155         }
156 
157         String javaHome = props.getProperty("java.home");
158         assertNotNull(javaHome);
159 
160         String sraJar = props.getProperty("sra.jar");
161         assertNotNull(sraJar);
162 
163         String keymasterApiJar = props.getProperty("keymaster-api.jar");
164         assertNotNull(keymasterApiJar);
165 
166         String keymasterClientJar = props.getProperty("keymaster-client.jar");
167         assertNotNull(keymasterClientJar);
168 
169         String trustStore = props.getProperty("trustStore");
170         assertNotNull(trustStore);
171         String trustStorePassword = props.getProperty("trustStorePassword");
172         assertNotNull(trustStorePassword);
173 
174         String targetTestClasses = props.getProperty("targetTestClasses");
175         assertNotNull(targetTestClasses);
176 
177         ProcessBuilder processBuilder = new ProcessBuilder(
178                 javaHome + "/bin/java",
179                 "-Dreactor.netty.http.server.accessLogEnabled=true",
180                 "-Djavax.net.ssl.trustStore=" + trustStore,
181                 "-Djavax.net.ssl.trustStorePassword=" + trustStorePassword,
182                 "-jar",
183                 "-Xdebug",
184                 "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8002",
185                 sraJar);
186         processBuilder.inheritIO();
187 
188         Map<String, String> environment = processBuilder.environment();
189         environment.put("LOADER_PATH", targetTestClasses + "," + keymasterApiJar + "," + keymasterClientJar);
190         environment.put("SPRING_PROFILES_ACTIVE", activeProfile);
191 
192         SRA = processBuilder.start();
193 
194         await().atMost(120, TimeUnit.SECONDS).pollInterval(3, TimeUnit.SECONDS).until(() -> {
195             boolean connected = false;
196             try (Socket socket = new Socket()) {
197                 socket.connect(new InetSocketAddress("0.0.0.0", 8080));
198                 connected = socket.isConnected();
199             } catch (ConnectException e) {
200                 // ignore
201             }
202             return connected;
203         });
204         assertDoesNotThrow(() -> WebClient.create(SRA_ADDRESS).get().getStatus());
205 
206         SRA_ROUTE_SERVICE.pushToSRA();
207     }
208 
209     @AfterAll
210     public static void stopSRA() throws InterruptedException {
211         if (SRA != null) {
212             SRA.destroy();
213             SRA.waitFor();
214         }
215     }
216 
217     protected static AuthPolicyTO getAuthPolicy() {
218         String authModule = "DefaultSyncopeAuthModule";
219         String description = "SRA auth policy";
220 
221         return POLICY_SERVICE.list(PolicyType.AUTH).stream().
222                 map(AuthPolicyTO.class::cast).
223                 filter(policy -> description.equals(policy.getName())
224                 && policy.getConf() instanceof DefaultAuthPolicyConf
225                 && ((DefaultAuthPolicyConf) policy.getConf()).getAuthModules().contains(authModule)).
226                 findFirst().
227                 orElseGet(() -> {
228                     DefaultAuthPolicyConf policyConf = new DefaultAuthPolicyConf();
229                     policyConf.getAuthModules().add(authModule);
230 
231                     AuthPolicyTO policy = new AuthPolicyTO();
232                     policy.setName(description);
233                     policy.setConf(policyConf);
234 
235                     Response response = POLICY_SERVICE.create(PolicyType.AUTH, policy);
236                     if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
237                         fail("Could not create Syncope Auth Policy");
238                     }
239 
240                     return POLICY_SERVICE.read(PolicyType.AUTH, response.getHeaderString(RESTHeaders.RESOURCE_KEY));
241                 });
242     }
243 
244     protected static AttrReleasePolicyTO getAttrReleasePolicy() {
245         String description = "SRA attr release policy";
246 
247         return POLICY_SERVICE.list(PolicyType.ATTR_RELEASE).stream().
248                 map(AttrReleasePolicyTO.class::cast).
249                 filter(policy -> description.equals(policy.getName())
250                 && policy.getConf() instanceof DefaultAttrReleasePolicyConf).
251                 findFirst().
252                 orElseGet(() -> {
253                     DefaultAttrReleasePolicyConf policyConf = new DefaultAttrReleasePolicyConf();
254                     policyConf.getAllowedAttrs().add("family_name");
255                     policyConf.getAllowedAttrs().add("name");
256                     policyConf.getAllowedAttrs().add("given_name");
257                     policyConf.getAllowedAttrs().add("email");
258                     policyConf.getAllowedAttrs().add("groups");
259 
260                     AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
261                     policy.setName(description);
262                     policy.setConf(policyConf);
263 
264                     Response response = POLICY_SERVICE.create(PolicyType.ATTR_RELEASE, policy);
265                     if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
266                         fail("Could not create Test Attr Release Policy");
267                     }
268 
269                     return POLICY_SERVICE.read(
270                             PolicyType.ATTR_RELEASE,
271                             response.getHeaderString(RESTHeaders.RESOURCE_KEY));
272                 });
273     }
274 
275     protected static ObjectNode checkGetResponse(
276             final CloseableHttpResponse response, final String originalRequestURI) throws IOException {
277 
278         assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
279 
280         assertEquals(MediaType.APPLICATION_JSON, response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
281 
282         JsonNode json = MAPPER.readTree(EntityUtils.toString(response.getEntity()));
283 
284         ObjectNode args = (ObjectNode) json.get("args");
285         assertEquals("value1", args.get("key1").asText());
286 
287         ArrayNode key2 = (ArrayNode) args.get("key2");
288         assertEquals("value2", key2.get(0).asText());
289         assertEquals("value3", key2.get(1).asText());
290 
291         assertEquals("an url encoded value: this!", args.get("key3").asText());
292 
293         ObjectNode headers = (ObjectNode) json.get("headers");
294         assertEquals(MediaType.TEXT_HTML, headers.get(HttpHeaders.ACCEPT).asText());
295         assertThat(headers.get("X-Forwarded-Host").asText(), is(oneOf("localhost:8080", "127.0.0.1:8080")));
296 
297         String withHost = StringUtils.substringBefore(originalRequestURI, "?");
298         String withIP = withHost.replace("localhost", "127.0.0.1");
299         assertThat(StringUtils.substringBefore(json.get("url").asText(), "?"), is(oneOf(withHost, withIP)));
300 
301         return headers;
302     }
303 
304     protected void checkLogout(final CloseableHttpResponse response) throws IOException {
305         assertEquals(HttpStatus.SC_NO_CONTENT, response.getStatusLine().getStatusCode());
306         assertEquals("true", response.getFirstHeader(LOGGED_OUT_HEADER).getValue());
307     }
308 }