Skip to content

Commit 57af4af

Browse files
committed
Fixes #471
1 parent d178a6c commit 57af4af

File tree

11 files changed

+117
-84
lines changed

11 files changed

+117
-84
lines changed

client/src/api/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,26 @@ export function connectServiceProviderToIdentityProvider(applicationManageIdenti
337337
return postPutJson("/api/v1/idp/connect", body, "PUT")
338338
}
339339

340+
export function disconnectServiceProviderToIdentityProvider(applicationManageIdentifier, entityType, idpManageIdentifier, message) {
341+
const body = {
342+
applicationManageIdentifier: applicationManageIdentifier,
343+
entityType: entityType,
344+
idpManageIdentifier: idpManageIdentifier,
345+
message
346+
};
347+
return postPutJson("/api/v1/idp/disconnect", body, "PUT")
348+
}
349+
350+
export function cancelServiceProviderConnectionRequest(applicationManageIdentifier, entityType, idpManageIdentifier, message) {
351+
const body = {
352+
applicationManageIdentifier: applicationManageIdentifier,
353+
entityType: entityType,
354+
idpManageIdentifier: idpManageIdentifier,
355+
message
356+
};
357+
return postPutJson("/api/v1/idp/cancel-connection-request", body, "PUT")
358+
}
359+
340360
//External Invite Proxy
341361
export function inviteRoles(organizationGUID, applicationManageId) {
342362
return fetchJson(`/api/v1/invite/roles/${organizationGUID}/${applicationManageId}`);

client/src/locale/en.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,8 @@ const en = {
10511051
flash: {
10521052
requestConnectionByMember: "Your request has been send",
10531053
makeConnection: "Application access has been set",
1054-
requestConnection: "Application access is requested"
1054+
requestConnection: "Application access is requested",
1055+
cancelConnectionRequest: "Your request has been cancelled"
10551056
}
10561057
},
10571058
appAccess: {

client/src/pages/ApplicationDetail.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import "./ApplicationDetail.scss";
22
import "../styles/access_card.scss";
33
import React, {useEffect, useState} from "react";
44
import {
5+
cancelServiceProviderConnectionRequest,
56
connectServiceProviderToIdentityProvider,
67
getPolicyByServiceProviderEntityId,
78
inviteRoles,
@@ -41,7 +42,7 @@ const confirmationModalOptions = {
4142
makeConnection: "makeConnection",
4243
requestConnection: "requestConnection",
4344
requestConnectionByMember: "requestConnectionByMember",
44-
disconnectConnection: "disconnectConnection",
45+
cancelConnection: "cancelConnection",
4546
requestDisconnectConnection: "requestDisconnectConnection",
4647
}
4748

@@ -263,7 +264,21 @@ const ApplicationDetail = ({anonymous, refreshUser}) => {
263264
});
264265
} else {
265266
cancelConfirmation();
266-
alert("cancel request");
267+
setLoading(true);
268+
const manageIdentifierOrg = user.identityProvider?.id || currentOrganization.manageIdentifier;
269+
cancelServiceProviderConnectionRequest(
270+
serviceProvider.id,
271+
serviceProvider.type,
272+
manageIdentifierOrg,
273+
message)
274+
.then(() => {
275+
//Because user is an useEffect dependency, everything will reload. Including change requests
276+
refreshUser(() => {
277+
//a small timeout to prevent flickering - cancelling requests does not happen that often
278+
setTimeout(() => setLoading(false), 75);
279+
});
280+
setFlash(I18n.t("applicationConnect.flash.cancelConnectionRequest"));
281+
});
267282
}
268283
}
269284

server/src/main/java/access/api/ConnectionController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ public ResponseEntity<Map<String, Object>> requestProductionStatus(User user,
205205
user.getName(), connection.getName(), jiraKey)),
206206
false,
207207
PathUpdateType.ADDITION,
208-
RequestType.ProductionStatusRequest);
208+
RequestType.ProductionStatusRequest,
209+
jiraKey);
209210
Map<String, Object> changeRequestResponse = manage.createChangeRequest(connection.getEnvironment(), changeRequest);
210211

211212
LOG.debug("Change request response from manage: " + changeRequestResponse);
@@ -276,7 +277,7 @@ private Connection productionReadyChangeRequests(Connection connection, User use
276277
Map<String, Object> auditData = Map.of("user", user.getEmail(),
277278
"notes", String.format("Production status requested by %s for %s. See Jira %s",
278279
user.getName(), connection.getName(), jiraKey));
279-
List<ChangeRequest> changeRequests = connectionProviderConverter.deduceChangeRequests(connection, provider, auditData);
280+
List<ChangeRequest> changeRequests = connectionProviderConverter.deduceChangeRequests(connection, provider, auditData, jiraKey);
280281
changeRequests.forEach(changeRequest -> manage.createChangeRequest(environment, changeRequest));
281282
//Now the tricky bit, we must fetch the changeRequest after they are created and return the data based on the provider
282283
connection.mergeMetaData(provider, true);

server/src/main/java/access/api/IdentityProviderController.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.ArrayList;
3737
import java.util.List;
3838
import java.util.Map;
39+
import java.util.Optional;
3940

4041
import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
4142
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;
@@ -46,6 +47,7 @@
4647
@Transactional
4748
@SecurityRequirement(name = OPEN_ID_SCHEME_NAME, scopes = {"openid"})
4849
@SecurityRequirement(name = API_TOKENS_SCHEME_NAME)
50+
@SuppressWarnings("unchecked")
4951
public class IdentityProviderController implements UserAccessRights {
5052

5153
private static final Log LOG = LogFactory.getLog(IdentityProviderController.class);
@@ -169,7 +171,8 @@ public ResponseEntity<Map<String, Object>> connect(User user, @RequestBody @Vali
169171
jiraKey)),
170172
true,
171173
PathUpdateType.ADDITION,
172-
RequestType.LinkRequest);
174+
RequestType.LinkRequest,
175+
jiraKey);
173176
manage.createChangeRequest(Environment.PROD, changeRequest);
174177

175178
return ResponseEntity.status(HttpStatus.CREATED).body(
@@ -223,11 +226,47 @@ public ResponseEntity<Map<String, Object>> disconnect(User user, @RequestBody @V
223226
jiraKey)),
224227
true,
225228
PathUpdateType.REMOVAL,
226-
RequestType.UnlinkRequest);
229+
RequestType.UnlinkRequest,
230+
jiraKey);
227231
manage.createChangeRequest(Environment.PROD, changeRequest);
228232

229233
return ResponseEntity.status(HttpStatus.CREATED).body(
230234
Map.of("status", HttpStatus.CREATED.value(), "jiraKey", jiraKey));
231235
}
232236

237+
@PutMapping({"/cancel-connection-request"})
238+
public ResponseEntity<Map<String, Object>> cancelConnectionRequest(User user, @RequestBody @Validated ConnectionRequest connectionRequest) {
239+
LOG.debug("/cancelConnectionRequest SP to IdP request by " + user.getEmail());
240+
241+
user = reinitializeUser(user, userRepository);
242+
243+
String idpManageIdentifier = connectionRequest.getIdpManageIdentifier();
244+
Organization organization = organizationRepository.findByManageIdentifier(idpManageIdentifier)
245+
.orElseThrow(() -> new NotFoundException("Organization with manageIdentifier not found: " + idpManageIdentifier));
246+
247+
Map<String, Object> serviceProvider = manage.providerById(connectionRequest.getEntityType(),
248+
connectionRequest.getApplicationManageIdentifier(), Environment.PROD);
249+
250+
confirmOrganizationMembership(user, organization, Authority.ADMIN);
251+
Map<String, Object> identityProvider = manage.providerById(EntityType.saml20_idp, idpManageIdentifier, Environment.PROD);
252+
253+
List<Map<String, Object>> changeRequests = manage.getChangeRequestsIdentityProvider(identityProvider);
254+
String serviceProviderEntityID = getEntityID(serviceProvider);
255+
List<Map<String, Object>> openChangeRequests = changeRequests.stream()
256+
.filter(changeRequest ->
257+
EntityType.saml20_idp.name().equals(changeRequest.get("type")) &&
258+
PathUpdateType.ADDITION.name().equalsIgnoreCase((String) changeRequest.get("pathUpdateType")) &&
259+
RequestType.LinkRequest.name().equalsIgnoreCase((String) changeRequest.get("requestType")) &&
260+
serviceProviderEntityID.equals(((Map<String, Map<String, String>>)
261+
changeRequest.getOrDefault("pathUpdates", Map.of()))
262+
.getOrDefault("allowedEntities", Map.of()).get("name")))
263+
.toList();
264+
//First delete all manage change request - this is most likely to succeed
265+
openChangeRequests.forEach(changeRequest -> manage.rejectChangeRequest(Environment.PROD, new ChangeRequest(changeRequest)));
266+
//Then update all Jira comments, this API is not so stable
267+
String comment = "Ticket can be closed by request of the requestor";
268+
openChangeRequests.forEach(changeRequest -> jiraClient.comment((String) changeRequest.get("ticketKey"), comment));
269+
270+
return Results.okResult();
271+
}
233272
}

server/src/main/java/access/api/OrganizationController.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,6 @@ public ResponseEntity<Organization> findMyOrganization(User user,
161161
if (organization.mergeMetaData(provider, false)) {
162162
organizationRepository.save(organization);
163163
}
164-
List<Map<String, Object>> changeRequests = manage.getChangeRequestsIdentityProvider(provider);
165-
organization.convertChangeRequests(changeRequests);
166-
167164
}
168165
return ResponseEntity.ok(organization);
169166
}

server/src/main/java/access/jira/JiraClient.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import org.springframework.core.io.ClassPathResource;
1313
import org.springframework.http.HttpEntity;
1414
import org.springframework.http.HttpHeaders;
15+
import org.springframework.http.HttpMethod;
1516
import org.springframework.http.MediaType;
17+
import org.springframework.http.ResponseEntity;
1618
import org.springframework.http.client.ClientHttpRequestInterceptor;
1719
import org.springframework.http.client.SimpleClientHttpRequestFactory;
1820
import org.springframework.stereotype.Service;
@@ -102,6 +104,18 @@ public String create(JiraIssue issue) {
102104
}
103105
}
104106

107+
public void comment(String jiraKey, String comment) {
108+
String commentUrl = config.getBaseUrl() + "/issue/" + jiraKey + "/comment";
109+
Map<String, String> body = Map.of("body", comment);
110+
HttpEntity<Object> commentRequestEntity = new HttpEntity<>(body, defaultHeaders);
111+
112+
LOG.info("Sending JSON {} to JIRA", body);
113+
114+
ResponseEntity<Map> responseEntity = restTemplate.exchange(commentUrl, HttpMethod.POST, commentRequestEntity, Map.class);
115+
116+
LOG.info("Response {} from JIRA", responseEntity.getBody());
117+
}
118+
105119
private String resolveIssueType() {
106120
return this.mappings.get(this.config.getEnvironment())
107121
.get("issueTypes").entrySet().stream()

server/src/main/java/access/manage/ChangeRequest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,22 @@ public class ChangeRequest implements Serializable {
3535

3636
private RequestType requestType;
3737

38+
private String ticketKey;
39+
40+
public ChangeRequest(Map<String, Object> manageChangeRequest) {
41+
this.id = (String) manageChangeRequest.get("id");
42+
this.type = (String) manageChangeRequest.get("type");
43+
this.metaDataId = (String) manageChangeRequest.get("metaDataId");
44+
}
45+
3846
public ChangeRequest(String metaDataId,
3947
EntityType entityType,
4048
Map<String, Object> pathUpdates,
4149
Map<String, Object> auditData,
4250
boolean incrementalChange,
4351
PathUpdateType pathUpdateType,
44-
RequestType requestType) {
52+
RequestType requestType,
53+
String ticketKey) {
4554
this.metaDataId = metaDataId;
4655
this.type = entityType.name();
4756
this.note = auditData != null ? (String) auditData.get("notes") : null;
@@ -50,6 +59,7 @@ public ChangeRequest(String metaDataId,
5059
this.incrementalChange = incrementalChange;
5160
this.pathUpdateType = pathUpdateType;
5261
this.requestType = requestType;
62+
this.ticketKey = ticketKey;
5363
}
5464

5565
}

server/src/main/java/access/manage/ConnectionProviderConverter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ public void convertContactPersons(Map<String, Object> applicationMetaData, Map<S
178178
//For all attributes that have been changed, we create a single ChangeRequest
179179
public List<ChangeRequest> deduceChangeRequests(Connection connection,
180180
Map<String, Object> currentProvider,
181-
Map<String, Object> auditData) {
181+
Map<String, Object> auditData,
182+
String jiraKey) {
182183
//We need to compare maps, the current data in Manage (e.g. currentProvider) and the new Data in Connection
183184
//So we need to convert the connection in to the new Map, without modifying the originalMap
184185
Map clonedProvider = deepClone(currentProvider);
@@ -197,7 +198,8 @@ public List<ChangeRequest> deduceChangeRequests(Connection connection,
197198
auditData,
198199
false,
199200
null,
200-
RequestType.Change);
201+
RequestType.Change,
202+
jiraKey);
201203
changeRequests.add(changeRequest);
202204
}
203205
return changeRequests;

server/src/main/java/access/model/Organization.java

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -176,73 +176,4 @@ public boolean mergeMetaData(Map<String, Object> provider, boolean force) {
176176
this.metaData.put("contactPersons", contactPersons);
177177
return true;
178178
}
179-
180-
@SuppressWarnings("unchecked")
181-
public void convertChangeRequests(List<Map<String, Object>> changeRequests) {
182-
//We don't convert the changeRequests to the same data as the metaData of an Organization as this proves
183-
//to be too difficult. We sort it out on the client
184-
this.changeRequests = changeRequests;
185-
// this.changeRequests = changeRequests.stream()
186-
// .map(changeRequest -> {
187-
// Map<String, Object> pathUpdates = (Map<String, Object>) changeRequest.get("pathUpdates");
188-
// Map<String, Object> provider = new HashMap<>();
189-
// //To prevent NullPointerException
190-
// provider.put("version", -1);
191-
//
192-
// Map<String, Object> data = new HashMap<>();
193-
// provider.put("data", data);
194-
//
195-
// Map<String, Object> metaDataFields = new HashMap<>();
196-
// data.put("metaDataFields", metaDataFields);
197-
//
198-
// pathUpdates.forEach((key, value) -> {
199-
// if (key.startsWith("metaDataFields")) {
200-
// //See src/test/resources/manage/change_request_large.json
201-
// key = key.substring("metaDataFields.".length());
202-
// metaDataFields.put(key, value);
203-
// } else {
204-
// //For pathUpdates to the stepupEntities, mfaEntities, disableConsent
205-
// String pathUpdateType = (String) changeRequest.get("pathUpdateType");
206-
// if (pathUpdateType == null) {
207-
// data.put(key, value);
208-
// } else {
209-
// //For incremental changes, we need the current value from the metaData and apply the change
210-
// Object originalValue = this.metaData.get(key);
211-
// if (originalValue instanceof List originalValueList) {
212-
// if (pathUpdateType.equals(PathUpdateType.ADDITION.name())) {
213-
// List newValue = new ArrayList(originalValueList);
214-
// if (value instanceof Map) {
215-
// newValue.add(value);
216-
// } else if (value instanceof List listValue) {
217-
// newValue.addAll(listValue);
218-
// }
219-
// data.put(key, newValue);
220-
// } else {
221-
// List<String> removedNames = value instanceof Map ? List.of((String) ((Map) value).get("name")) :
222-
// ((List) value).stream().map(m -> ((Map) m).get("name")).toList();
223-
// List newValue = originalValueList.stream()
224-
// .filter(m -> !removedNames.contains(((Map) m).get("name")))
225-
// .toList();
226-
// data.put(key, newValue);
227-
// }
228-
// } else {
229-
// data.put(key, value);
230-
// }
231-
// }
232-
// }
233-
// });
234-
// Organization organization = new Organization();
235-
// organization.mergeMetaData(provider, true);
236-
// Map<String, Object> connectionMetaData = organization.getMetaData();
237-
// //Now clean up all keys where the value is empty
238-
// connectionMetaData.entrySet().removeIf(entry -> isEmpty(entry.getValue()));
239-
// //copy some of the auditData for display purposes and revoke functionality
240-
// List.of("id", "metaDataId", "type", "auditData", "created")
241-
// .forEach(attr -> connectionMetaData.put(attr, changeRequest.get(attr)));
242-
// return connectionMetaData;
243-
// })
244-
// .toList();
245-
}
246-
247-
248179
}

0 commit comments

Comments
 (0)