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.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
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.assertTrue;
27  
28  import com.fasterxml.jackson.databind.MappingIterator;
29  import com.fasterxml.jackson.dataformat.csv.CsvMapper;
30  import com.fasterxml.jackson.dataformat.csv.CsvSchema;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.util.Date;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.UUID;
37  import javax.ws.rs.core.HttpHeaders;
38  import javax.ws.rs.core.Response;
39  import org.apache.cxf.jaxrs.client.Client;
40  import org.apache.cxf.jaxrs.client.WebClient;
41  import org.apache.syncope.client.lib.SyncopeClient;
42  import org.apache.syncope.common.lib.Attr;
43  import org.apache.syncope.common.lib.SyncopeConstants;
44  import org.apache.syncope.common.lib.request.AnyObjectCR;
45  import org.apache.syncope.common.lib.to.AnyObjectTO;
46  import org.apache.syncope.common.lib.to.PagedResult;
47  import org.apache.syncope.common.lib.to.ProvisioningReport;
48  import org.apache.syncope.common.lib.to.PullTaskTO;
49  import org.apache.syncope.common.lib.to.PushTaskTO;
50  import org.apache.syncope.common.lib.to.ReconStatus;
51  import org.apache.syncope.common.lib.to.UserTO;
52  import org.apache.syncope.common.lib.types.AnyTypeKind;
53  import org.apache.syncope.common.lib.types.MatchType;
54  import org.apache.syncope.common.lib.types.ResourceOperation;
55  import org.apache.syncope.common.lib.types.UnmatchingRule;
56  import org.apache.syncope.common.rest.api.RESTHeaders;
57  import org.apache.syncope.common.rest.api.beans.AnyQuery;
58  import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
59  import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
60  import org.apache.syncope.common.rest.api.beans.ReconQuery;
61  import org.apache.syncope.common.rest.api.service.ReconciliationService;
62  import org.apache.syncope.fit.AbstractITCase;
63  import org.identityconnectors.framework.common.objects.OperationalAttributes;
64  import org.identityconnectors.framework.common.objects.Uid;
65  import org.junit.jupiter.api.Test;
66  import org.springframework.jdbc.core.JdbcTemplate;
67  
68  public class ReconciliationITCase extends AbstractITCase {
69  
70      @Test
71      public void push() {
72          // 1. create printer, with no resources
73          AnyObjectCR printerCR = AnyObjectITCase.getSample("reconciliation");
74          printerCR.getResources().clear();
75          AnyObjectTO printer = createAnyObject(printerCR).getEntity();
76          assertNotNull(printer.getKey());
77  
78          // 2. verify no printer with that name is on the external resource's db
79          JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
80          assertEquals(0, jdbcTemplate.queryForList(
81                  "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
82  
83          // 3. verify reconciliation status
84          ReconStatus status = RECONCILIATION_SERVICE.status(
85                  new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).anyKey(printer.getName()).build());
86          assertNotNull(status);
87          assertEquals(AnyTypeKind.ANY_OBJECT, status.getAnyTypeKind());
88          assertEquals(printer.getKey(), status.getAnyKey());
89          assertEquals(MatchType.ANY, status.getMatchType());
90          assertNotNull(status.getOnSyncope());
91          assertNull(status.getOnResource());
92  
93          // 4. push
94          PushTaskTO pushTask = new PushTaskTO();
95          pushTask.setPerformCreate(true);
96          pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
97          RECONCILIATION_SERVICE.push(new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).
98                  anyKey(printer.getKey()).build(), pushTask);
99  
100         // 5. verify that printer is now propagated
101         assertEquals(1, jdbcTemplate.queryForList(
102                 "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
103 
104         // 6. verify resource was not assigned
105         printer = ANY_OBJECT_SERVICE.read(printer.getKey());
106         assertTrue(printer.getResources().isEmpty());
107 
108         // 7. verify reconciliation status
109         status = RECONCILIATION_SERVICE.status(
110                 new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).anyKey(printer.getName()).build());
111         assertNotNull(status);
112         assertNotNull(status.getOnSyncope());
113         assertNotNull(status.getOnResource());
114 
115         // __ENABLE__ management depends on the actual connector...
116         Attr enable = status.getOnSyncope().getAttr(OperationalAttributes.ENABLE_NAME).orElse(null);
117         if (enable != null) {
118             status.getOnSyncope().getAttrs().remove(enable);
119         }
120         // FIQL is always null for Syncope
121         assertNull(status.getOnSyncope().getFiql());
122         assertNotNull(status.getOnResource().getFiql());
123         status.getOnResource().setFiql(null);
124         assertEquals(status.getOnSyncope(), status.getOnResource());
125     }
126 
127     @Test
128     public void pull() {
129         // 1. create printer, with no resources
130         AnyObjectCR printerCR = AnyObjectITCase.getSample("reconciliation");
131         printerCR.getResources().clear();
132         AnyObjectTO printer = createAnyObject(printerCR).getEntity();
133         assertNotNull(printer.getKey());
134         assertNotEquals("Nowhere", printer.getPlainAttr("location").get().getValues().get(0));
135 
136         // 2. add row into the external resource's table, with same name
137         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
138         jdbcTemplate.update(
139                 "INSERT INTO TESTPRINTER (id, printername, location, deleted, lastmodification) VALUES (?,?,?,?,?)",
140                 printer.getKey(), printer.getName(), "Nowhere", false, new Date());
141 
142         // 3. verify reconciliation status
143         ReconStatus status = RECONCILIATION_SERVICE.status(
144                 new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).anyKey(printer.getName()).build());
145         assertNotNull(status);
146         assertNotNull(status.getOnSyncope());
147         assertNotNull(status.getOnResource());
148         assertNotEquals(status.getOnSyncope().getAttr("LOCATION"), status.getOnResource().getAttr("LOCATION"));
149 
150         // 4. pull
151         PullTaskTO pullTask = new PullTaskTO();
152         pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
153         pullTask.setPerformUpdate(true);
154         RECONCILIATION_SERVICE.pull(new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).
155                 anyKey(printer.getName()).build(), pullTask);
156 
157         // 5. verify reconciliation result (and resource is still not assigned)
158         printer = ANY_OBJECT_SERVICE.read(printer.getKey());
159         assertEquals("Nowhere", printer.getPlainAttr("location").get().getValues().get(0));
160         assertTrue(printer.getResources().isEmpty());
161     }
162 
163     @Test
164     public void importSingle() {
165         // 1. add row into the external resource's table
166         String externalKey = UUID.randomUUID().toString();
167         String externalName = "printer" + getUUIDString();
168 
169         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
170         jdbcTemplate.update(
171                 "INSERT INTO TESTPRINTER (id, printername, location, deleted, lastmodification) VALUES (?,?,?,?,?)",
172                 externalKey, externalName, "Nowhere", false, new Date());
173 
174         // 2. verify reconciliation status
175         ReconStatus status = RECONCILIATION_SERVICE.status(
176                 new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).fiql("ID==" + externalKey).build());
177         assertNotNull(status);
178         assertNull(status.getAnyTypeKind());
179         assertNull(status.getAnyKey());
180         assertNull(status.getMatchType());
181         assertNull(status.getOnSyncope());
182         assertNotNull(status.getOnResource());
183         assertEquals(externalKey, status.getOnResource().getAttr(Uid.NAME).get().getValues().get(0));
184         assertEquals(externalName, status.getOnResource().getAttr("PRINTERNAME").get().getValues().get(0));
185 
186         // 3. pull
187         PullTaskTO pullTask = new PullTaskTO();
188         pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
189         pullTask.setPerformCreate(true);
190         RECONCILIATION_SERVICE.pull(
191                 new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).fiql("ID==" + externalKey).build(), pullTask);
192 
193         // 4. verify reconciliation result
194         AnyObjectTO printer = ANY_OBJECT_SERVICE.read(PRINTER, externalName);
195         assertNotNull(printer);
196     }
197 
198     @Test
199     public void importCSV() {
200         ReconciliationService service = ADMIN_CLIENT.getService(ReconciliationService.class);
201         Client client = WebClient.client(service);
202         client.type(RESTHeaders.TEXT_CSV);
203 
204         CSVPullSpec spec = new CSVPullSpec.Builder(AnyTypeKind.USER.name(), "username").build();
205         InputStream csv = getClass().getResourceAsStream("/test1.csv");
206 
207         List<ProvisioningReport> results = service.pull(spec, csv);
208         assertEquals(AnyTypeKind.USER.name(), results.get(0).getAnyType());
209         assertNotNull(results.get(0).getKey());
210         assertEquals("donizetti", results.get(0).getName());
211         assertEquals("donizetti", results.get(0).getUidValue());
212         assertEquals(ResourceOperation.CREATE, results.get(0).getOperation());
213         assertEquals(ProvisioningReport.Status.SUCCESS, results.get(0).getStatus());
214 
215         UserTO donizetti = USER_SERVICE.read(results.get(0).getKey());
216         assertNotNull(donizetti);
217         assertEquals("Gaetano", donizetti.getPlainAttr("firstname").get().getValues().get(0));
218         assertEquals(1, donizetti.getPlainAttr("loginDate").get().getValues().size());
219 
220         UserTO cimarosa = USER_SERVICE.read(results.get(1).getKey());
221         assertNotNull(cimarosa);
222         assertEquals("Domenico Cimarosa", cimarosa.getPlainAttr("fullname").get().getValues().get(0));
223         assertEquals(2, cimarosa.getPlainAttr("loginDate").get().getValues().size());
224     }
225 
226     @Test
227     public void exportCSV() throws IOException {
228         ReconciliationService service = ADMIN_CLIENT.getService(ReconciliationService.class);
229         Client client = WebClient.client(service);
230         client.accept(RESTHeaders.TEXT_CSV);
231 
232         AnyQuery anyQuery = new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
233                 fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("*ini").query()).
234                 page(1).
235                 size(1000).
236                 orderBy("username ASC").
237                 build();
238 
239         CSVPushSpec spec = new CSVPushSpec.Builder(AnyTypeKind.USER.name()).ignorePaging(true).
240                 field("username").
241                 field("status").
242                 plainAttr("firstname").
243                 plainAttr("surname").
244                 plainAttr("email").
245                 plainAttr("loginDate").
246                 build();
247 
248         Response response = service.push(anyQuery, spec);
249         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
250         assertEquals(
251                 "attachment; filename=" + SyncopeConstants.MASTER_DOMAIN + ".csv",
252                 response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION));
253 
254         PagedResult<UserTO> users = USER_SERVICE.search(anyQuery);
255         assertNotNull(users);
256 
257         MappingIterator<Map<String, String>> reader = new CsvMapper().readerFor(Map.class).
258                 with(CsvSchema.emptySchema().withHeader()).readValues((InputStream) response.getEntity());
259 
260         int rows = 0;
261         for (; reader.hasNext(); rows++) {
262             Map<String, String> row = reader.next();
263 
264             assertEquals(users.getResult().get(rows).getUsername(), row.get("username"));
265             assertEquals(users.getResult().get(rows).getStatus(), row.get("status"));
266 
267             switch (row.get("username")) {
268                 case "rossini":
269                     assertEquals(spec.getNullValue(), row.get("email"));
270                     assertTrue(row.get("loginDate").contains(spec.getArrayElementSeparator()));
271                     break;
272 
273                 case "verdi":
274                     assertEquals("verdi@syncope.org", row.get("email"));
275                     assertEquals(spec.getNullValue(), row.get("loginDate"));
276                     break;
277 
278                 case "bellini":
279                     assertEquals(spec.getNullValue(), row.get("email"));
280                     assertFalse(row.get("loginDate").contains(spec.getArrayElementSeparator()));
281                     break;
282 
283                 default:
284                     break;
285             }
286         }
287         assertEquals(rows, users.getTotalCount());
288     }
289 }