Skip to content

Commit 5ecfe49

Browse files
feat(admin): add /reset-client-secret endpoint and fix config persist… (#7304)
* feat(admin): add /reset-client-secret endpoint and fix config persistence in add-client * test(admin): Add missing test for admin controller and fix typo --------- Co-authored-by: Angel Montenegro <a.montenegro@orcid.org> Co-authored-by: Daniel Palafox <daniel.p4l4fox@gmail.com>
1 parent 38dace2 commit 5ecfe49

5 files changed

Lines changed: 217 additions & 37 deletions

File tree

orcid-core/src/main/java/org/orcid/core/manager/v3/ClientManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ public interface ClientManager {
88

99
Client createPublicClient(Client newClient);
1010

11+
Client createWithConfigValues(Client newClient);
12+
1113
Client edit(Client existingClient, boolean updateConfigValues);
12-
14+
1315
Boolean resetClientSecret(String clientId);
16+
17+
String resetAndGetClientSecret(String clientId);
1418
}

orcid-core/src/main/java/org/orcid/core/manager/v3/impl/ClientManagerImpl.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,22 @@ public class ClientManagerImpl implements ClientManager {
8282
@Override
8383
@Transactional
8484
public Client create(Client newClient) throws IllegalArgumentException {
85-
return create(newClient, false);
85+
return create(newClient, false, false);
8686
}
8787

8888
@Override
8989
@Transactional
9090
public Client createPublicClient(Client newClient) {
91-
return create(newClient, true);
91+
return create(newClient, true, false);
9292
}
9393

94-
private Client create(Client newClient, boolean publicClient) {
94+
@Override
95+
@Transactional
96+
public Client createWithConfigValues(Client newClient) {
97+
return create(newClient, false, true);
98+
}
99+
100+
private Client create(Client newClient, boolean publicClient, boolean addConfigValues) {
95101
// If the member id comes in the newClient, use that one, if not, use the active source id
96102
String memberId = PojoUtil.isEmpty(newClient.getGroupProfileId()) ? null : newClient.getGroupProfileId();
97103
if(memberId == null) {
@@ -115,7 +121,6 @@ private Client create(Client newClient, boolean publicClient) {
115121
}
116122

117123
ClientDetailsEntity newEntity = jpaJaxbClientAdapter.toEntity(newClient);
118-
Date now = new Date();
119124
newEntity.setId(appIdGenerationManager.createNewAppId());
120125
newEntity.setClientSecretForJpa(encryptionManager.encryptForInternalUse(UUID.randomUUID().toString()), true);
121126
newEntity.setGroupProfileId(memberId);
@@ -177,6 +182,11 @@ private Client create(Client newClient, boolean publicClient) {
177182
}
178183
newEntity.setClientScopes(clientScopeEntities);
179184

185+
if (addConfigValues) {
186+
newEntity.setUserOBOEnabled(newClient.isUserOBOEnabled());
187+
refreshGrantTypesForObo(newEntity, newClient.isOboEnabled());
188+
}
189+
180190
try {
181191
clientDetailsDao.persist(newEntity);
182192
} catch (Exception e) {
@@ -247,7 +257,29 @@ public Boolean doInTransaction(TransactionStatus status) {
247257
}
248258
});
249259
}
250-
260+
261+
@Override
262+
@Transactional
263+
public String resetAndGetClientSecret(String clientId) {
264+
try {
265+
String newSecret = encryptionManager.encryptForInternalUse(UUID.randomUUID().toString());
266+
clientSecretDao.revokeAllKeys(clientId);
267+
boolean created = clientSecretDao.createClientSecret(clientId, newSecret);
268+
if (created) {
269+
clientDetailsDao.updateLastModified(clientId);
270+
String sourceId = sourceManager.retrieveActiveSourceId();
271+
profileLastModifiedDao.updateLastModifiedDateWithoutResult(sourceId);
272+
} else {
273+
LOGGER.warn("Client secret creation failed for client {}", clientId);
274+
}
275+
276+
return encryptionManager.decryptForInternalUse(newSecret);
277+
} catch (Exception e) {
278+
LOGGER.error("Unable to reset client secret for client {}", clientId, e);
279+
throw e;
280+
}
281+
}
282+
251283
private void refreshGrantTypesForObo(ClientDetailsEntity clientDetails, boolean enableObo) {
252284
boolean oboAlreadyEnabled = false;
253285
Iterator<ClientAuthorisedGrantTypeEntity> grantTypes = clientDetails.getClientAuthorizedGrantTypes().iterator();

orcid-core/src/test/java/org/orcid/core/manager/v3/ClientManagerTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,74 @@ public void resetClientSecret() {
387387
assertEquals(secret3, client.getDecryptedSecret());
388388
}
389389

390+
@Test
391+
@Transactional
392+
public void saveClientWithConfigValuesTest() {
393+
// Create a new client
394+
String seed = RandomStringUtils.randomAlphanumeric(15);
395+
Client client = getClient(seed, MEMBER_ID);
396+
397+
client.setAllowAutoDeprecate(false);
398+
client.setUserOBOEnabled(true);
399+
client.setOboEnabled(true);
400+
401+
Client newClient = clientManager.createWithConfigValues(client);
402+
// Check config options where saved
403+
assertTrue(newClient.getId().startsWith("APP-"));
404+
assertEquals("authentication-provider-id " + seed, newClient.getAuthenticationProviderId());
405+
assertFalse(newClient.isAllowAutoDeprecate());
406+
assertTrue(newClient.isUserOBOEnabled());
407+
assertTrue(newClient.isOboEnabled());
408+
409+
seed = RandomStringUtils.randomAlphanumeric(15);
410+
client = getClient(seed, MEMBER_ID);
411+
412+
client.setAllowAutoDeprecate(true);
413+
client.setUserOBOEnabled(true);
414+
client.setOboEnabled(false);
415+
416+
newClient = clientManager.createWithConfigValues(client);
417+
// Check config options where saved
418+
assertTrue(newClient.getId().startsWith("APP-"));
419+
assertEquals("authentication-provider-id " + seed, newClient.getAuthenticationProviderId());
420+
assertTrue(newClient.isAllowAutoDeprecate());
421+
assertTrue(newClient.isUserOBOEnabled());
422+
assertFalse(newClient.isOboEnabled());
423+
}
424+
425+
@Test
426+
public void resetAndGetClientSecretTest() {
427+
String clientId = "APP-5555555555555556";
428+
// Get an existing client
429+
Client client = clientManagerReadOnly.get(clientId);
430+
assertNotNull(client);
431+
assertNotNull(client.getDecryptedSecret());
432+
assertFalse(PojoUtil.isEmpty(client.getDecryptedSecret()));
433+
assertTrue(client.getDecryptedSecret().length() > 1);
434+
String secret1 = client.getDecryptedSecret();
435+
436+
// Reset it one time
437+
String newSecret = clientManager.resetAndGetClientSecret(clientId);
438+
assertFalse(PojoUtil.isEmpty(newSecret));
439+
assertNotEquals(secret1, newSecret);
440+
String secret2 = newSecret;
441+
442+
// Reset it the second time
443+
newSecret = clientManager.resetAndGetClientSecret(clientId);
444+
assertFalse(PojoUtil.isEmpty(client.getDecryptedSecret()));
445+
assertNotEquals(secret1, newSecret);
446+
assertNotEquals(secret2, newSecret);
447+
String secret3 = newSecret;
448+
449+
// Update the client and fetch secret again to confirm it didnt changed
450+
client.setName("Updated name");
451+
client = clientManager.edit(client, false);
452+
assertEquals("Updated name", client.getName());
453+
assertFalse(PojoUtil.isEmpty(client.getDecryptedSecret()));
454+
assertTrue(client.getDecryptedSecret().length() > 1);
455+
assertEquals(secret3, client.getDecryptedSecret());
456+
}
457+
390458
private void validateClientConfigSettings(ClientDetailsEntity entity, Date lastTimeEntityWasModified) {
391459
assertNotNull(entity.getAuthorizedGrantTypes());
392460
assertEquals(4, entity.getClientAuthorizedGrantTypes().size());

orcid-web/src/main/java/org/orcid/frontend/web/controllers/AdminController.java

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import org.orcid.core.manager.ProfileEntityCacheManager;
2828
import org.orcid.core.manager.TwoFactorAuthenticationManager;
2929
import org.orcid.core.manager.v3.*;
30+
import org.orcid.core.manager.v3.read_only.ClientManagerReadOnly;
3031
import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly;
31-
import org.orcid.core.utils.PasswordResetToken;
3232
import org.orcid.core.utils.VerifyEmailUtils;
3333
import org.orcid.frontend.email.RecordEmailSender;
3434
import org.orcid.frontend.web.util.PasswordConstants;
@@ -56,12 +56,15 @@
5656
import org.slf4j.Logger;
5757
import org.slf4j.LoggerFactory;
5858
import org.springframework.beans.factory.annotation.Value;
59+
import org.springframework.http.HttpStatus;
60+
import org.springframework.http.ResponseEntity;
5961
import org.springframework.stereotype.Controller;
6062
import org.springframework.web.bind.annotation.ModelAttribute;
6163
import org.springframework.web.bind.annotation.RequestBody;
6264
import org.springframework.web.bind.annotation.RequestMapping;
6365
import org.springframework.web.bind.annotation.RequestMethod;
6466
import org.springframework.web.bind.annotation.ResponseBody;
67+
import org.springframework.web.bind.annotation.PostMapping;
6568
import org.springframework.web.servlet.ModelAndView;
6669

6770
/**
@@ -107,6 +110,9 @@ public class AdminController extends BaseController {
107110
@Resource(name = "clientManagerV3")
108111
private ClientManager clientManager;
109112

113+
@Resource(name = "clientManagerReadOnlyV3")
114+
private ClientManagerReadOnly clientManagerReadOnly;
115+
110116
@Resource(name = "profileDaoReadOnly")
111117
private ProfileDao profileDaoReadOnly;
112118

@@ -686,7 +692,7 @@ public Map<String, String> findIdByEmailHelper(String csvEmails) {
686692

687693
/**
688694
* Reset password validate
689-
*
695+
*
690696
* @throws IllegalAccessException
691697
* @throws UnsupportedEncodingException
692698
*/
@@ -1262,45 +1268,92 @@ private String getOrcidFromParam(String orcidOrEmail) {
12621268
}
12631269

12641270
@RequestMapping(value = "/add-client.json", method = RequestMethod.POST)
1265-
@Produces(value = { MediaType.APPLICATION_JSON })
1266-
public @ResponseBody Client createClient(HttpServletRequest serverRequest, HttpServletResponse response, @RequestBody Client client) throws IllegalAccessException {
1271+
@Produces(value = {MediaType.APPLICATION_JSON})
1272+
@ResponseBody
1273+
public Client createClient(HttpServletRequest serverRequest, HttpServletResponse response, @RequestBody Client client) throws IllegalAccessException {
12671274
isAdmin(serverRequest, response);
1268-
if(client == null) {
1275+
1276+
List<String> errors = new ArrayList<>();
1277+
1278+
if (client == null) {
12691279
client = new Client();
1270-
client.getErrors().add("Client object cannot be null");
1271-
} else if(client.getMemberId() == null || PojoUtil.isEmpty(client.getMemberId())) {
1272-
client.getErrors().add("Member ID is requiered");
1280+
errors.add("Client object cannot be null");
1281+
} else if (client.getMemberId() == null || PojoUtil.isEmpty(client.getMemberId())) {
1282+
errors.add("Member ID is required");
12731283
} else if (profileDaoReadOnly.getGroupType(client.getMemberId().getValue()) == null) {
1274-
client.getErrors().add("Member with ID " + client.getMemberId().getValue() + " does not exists");
1275-
} else if(client.getDisplayName() == null || PojoUtil.isEmpty(client.getDisplayName())) {
1276-
client.getErrors().add("Display name is requiered");
1277-
} else if(client.getShortDescription() == null || PojoUtil.isEmpty(client.getShortDescription())) {
1278-
client.getErrors().add("Description is requiered");
1279-
} else if(client.getWebsite() == null || PojoUtil.isEmpty(client.getWebsite())) {
1280-
client.getErrors().add("Website is requiered");
1281-
} else if(client.getRedirectUris() == null || client.getRedirectUris().isEmpty()) {
1282-
client.getErrors().add("Redirect URIs are requiered");
1284+
errors.add("Member with ID " + client.getMemberId().getValue() + " does not exists");
1285+
} else if (client.getDisplayName() == null || PojoUtil.isEmpty(client.getDisplayName())) {
1286+
errors.add("Display name is required");
1287+
} else if (client.getShortDescription() == null || PojoUtil.isEmpty(client.getShortDescription())) {
1288+
errors.add("Description is required");
1289+
} else if (client.getWebsite() == null || PojoUtil.isEmpty(client.getWebsite())) {
1290+
errors.add("Website is required");
1291+
} else if (client.getRedirectUris() == null || client.getRedirectUris().isEmpty()) {
1292+
errors.add("Redirect URIs are required");
12831293
} else {
12841294
// Validate the redirect uris are valid
1285-
for(RedirectUri r : client.getRedirectUris()) {
1286-
if(r.getType() == null || PojoUtil.isEmpty(r.getType())) {
1287-
client.getErrors().add("Redirect uri type missing on redirect uri " + r.getValue().getValue());
1295+
for (RedirectUri r : client.getRedirectUris()) {
1296+
if (r.getType() == null || PojoUtil.isEmpty(r.getType())) {
1297+
errors.add("Redirect uri type missing on redirect uri " + r.getValue().getValue());
12881298
}
12891299
}
1290-
if(client.getErrors().isEmpty()) {
1300+
1301+
if (errors.isEmpty()) {
12911302
org.orcid.jaxb.model.v3.release.client.Client newClient = client.toModelObject();
12921303
try {
1293-
newClient = clientManager.create(newClient);
1304+
newClient = clientManager.createWithConfigValues(newClient);
12941305
} catch (Exception e) {
12951306
LOGGER.error(e.getMessage());
1296-
String errorDesciption = getMessage("manage.developer_tools.group.cannot_create_client") + " " + e.getMessage();
1307+
String errorDescription = getMessage("manage.developer_tools.group.cannot_create_client") + " " + e.getMessage();
12971308
client.setErrors(new ArrayList<String>());
1298-
client.getErrors().add(errorDesciption);
1309+
client.getErrors().add(errorDescription);
12991310
return client;
13001311
}
13011312
client = Client.fromModelObject(newClient);
13021313
}
13031314
}
1315+
1316+
if (!errors.isEmpty()) {
1317+
client.setErrors(errors);
1318+
return client;
1319+
}
1320+
1321+
13041322
return client;
13051323
}
1324+
1325+
1326+
@PostMapping(value = "/reset-client-secret.json", produces = MediaType.APPLICATION_JSON)
1327+
public ResponseEntity<Map<String, String>> resetClientSecret(HttpServletRequest serverRequest, HttpServletResponse response, @RequestBody Client client) throws IllegalAccessException {
1328+
isAdmin(serverRequest, response);
1329+
1330+
if (client == null || PojoUtil.isEmpty(client.getClientId())) {
1331+
return ResponseEntity
1332+
.badRequest()
1333+
.body(Map.of("error", "Client ID is required"));
1334+
}
1335+
1336+
String clientId = client.getClientId().getValue();
1337+
1338+
org.orcid.jaxb.model.v3.release.client.Client existingClient = clientManagerReadOnly.get(clientId);
1339+
if (existingClient == null) {
1340+
return ResponseEntity
1341+
.status(HttpStatus.NOT_FOUND)
1342+
.body(Map.of("error", "Client “" + clientId + "” not found"));
1343+
}
1344+
1345+
try {
1346+
String newSecret = clientManager.resetAndGetClientSecret(clientId);
1347+
return ResponseEntity
1348+
.ok(Map.of(
1349+
"clientId", clientId,
1350+
"newSecret", newSecret
1351+
));
1352+
} catch (Exception e) {
1353+
LOGGER.error("Error resetting secret for client {}", clientId, e);
1354+
return ResponseEntity
1355+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
1356+
.body(Map.of("error", "Failed to reset client secret"));
1357+
}
1358+
}
13061359
}

0 commit comments

Comments
 (0)