Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.

Commit

Permalink
feat: Add description, lastUpdatedTime, and lastUpdatedBy to secrets
Browse files Browse the repository at this point in the history
Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com>
  • Loading branch information
eamansour committed Oct 29, 2024
1 parent 98b5eae commit be4e991
Show file tree
Hide file tree
Showing 36 changed files with 1,058 additions and 325 deletions.
8 changes: 4 additions & 4 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"hashed_secret": "0ea7458942ab65e0a340cf4fd28ca00d93c494f3",
"is_secret": false,
"is_verified": false,
"line_number": 321,
"line_number": 710,
"type": "Secret Keyword",
"verified_result": null
}
Expand All @@ -129,15 +129,15 @@
"hashed_secret": "1beb7496ebbe82c61151be093956d83dac625c13",
"is_secret": false,
"is_verified": false,
"line_number": 285,
"line_number": 293,
"type": "Secret Keyword",
"verified_result": null
},
{
"hashed_secret": "89e7fc0c50091804bfeb26cddefc0e701dd60fab",
"is_secret": false,
"is_verified": false,
"line_number": 724,
"line_number": 732,
"type": "Secret Keyword",
"verified_result": null
}
Expand All @@ -147,7 +147,7 @@
"hashed_secret": "1beb7496ebbe82c61151be093956d83dac625c13",
"is_secret": false,
"is_verified": false,
"line_number": 636,
"line_number": 670,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import dev.galasa.framework.api.common.IBeanValidator;
import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;
import dev.galasa.framework.api.common.resources.BaseResourceValidator;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;

import javax.servlet.http.HttpServletResponse;

public class TokenPayloadValidator implements IBeanValidator<TokenPayload> {
public class TokenPayloadValidator extends BaseResourceValidator implements IBeanValidator<TokenPayload> {

@Override
public void validate(TokenPayload tokenPayload) throws InternalServletException {
Expand All @@ -36,21 +37,4 @@ public void validate(TokenPayload tokenPayload) throws InternalServletException
throw new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST);
}
}

/**
* Checks whether a given string contains only alphanumeric characters, '-', and '_'
*
* @param str the string to validate
* @return true if the string contains only alphanumeric characters, '-', and '_', or false otherwise
*/
private boolean isAlphanumWithDashes(String str) {
boolean isValid = true;
for (char c : str.toCharArray()) {
if (!Character.isLetterOrDigit(c) && c != '-' && c != '_') {
isValid = false;
break;
}
}
return isValid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public enum ServletErrorMessage {
GAL5082_NO_LOGINID_PARAM_PROVIDED (5082, "E: A request to get the user details failed. The request did not supply a ‘loginId’ filter. A ‘loginId’ query parameter with a value of : ‘me’ was expected. This problem is caused by the client program sending a bad request. Please report this problem to the owner of your client program."),

// Secrets APIs...
GAL5092_INVALID_SECRET_NAME_PROVIDED (5092, "E: Invalid secret name provided. The name of a Galasa secret cannot be empty or contain only spaces or tabs. Check your request payload and try again."),
GAL5092_INVALID_SECRET_NAME_PROVIDED (5092, "E: Invalid secret name provided. The name of a Galasa secret cannot be empty or contain only spaces or tabs, and must only contain characters in the Latin-1 character set. Check your request payload and try again."),
GAL5093_ERROR_SECRET_NOT_FOUND (5093, "E: Unable to retrieve a secret with the given name. No such secret exists. Check your request query parameters and try again."),
GAL5094_FAILED_TO_GET_SECRET_FROM_CREDS (5094, "E: Failed to retrieve a secret with the given name from the credentials store. The credentials store might be badly configured or could be experiencing a temporary issue. Report the problem to your Galasa Ecosystem owner."),
GAL5095_ERROR_PASSWORD_AND_TOKEN_PROVIDED (5095, "E: Invalid secret payload provided. The ''password'' and ''token'' fields are mutually exclusive and cannot be provided in the same secret. Check your request payload and try again."),
Expand All @@ -138,6 +138,7 @@ public enum ServletErrorMessage {
GAL5099_ERROR_MISSING_REQUIRED_SECRET_FIELD (5099, "E: Invalid secret payload provided. The ''{0}'' type was provided but the required ''{1}'' field was missing. Check your request payload and try again."),
GAL5100_ERROR_UNEXPECTED_SECRET_FIELD_PROVIDED (5100, "E: Invalid secret payload provided. An unexpected field was given to update a ''{0}'' secret. Only the following fields can be provided to update this secret: ''{1}''. Check your request payload and try again."),
GAL5101_ERROR_UNEXPECTED_SECRET_TYPE_DETECTED (5101, "E: Unknown secret type detected. A secret retrieved from the credentials store is in an unknown or unsupported format. Report the problem to your Galasa Ecosystem owner."),
GAL5102_INVALID_SECRET_DESCRIPTION_PROVIDED (5102, "E: Invalid secret description provided. The description should not only contain spaces or tabs. When provided, it must contain characters in the Latin-1 character set. Report the problem to your Galasa Ecosystem owner."),
;


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.framework.api.common.resources;

/**
* A base validator class that contains commonly-used validation methods
*/
public class BaseResourceValidator {

/**
* Checks whether a given string is in valid Latin-1 format (e.g. characters in the range 0 - 255)
*
* @param str the string to validate
* @return true if the string is in valid Latin-1 format, or false otherwise
*/
public boolean isLatin1(String str) {
boolean isValidLatin1 = true;
for (char i = 0; i < str.length(); i++) {
if (str.charAt(i) > 255) {
isValidLatin1 = false;
break;
}
}
return isValidLatin1;
}

/**
* Checks whether a given string contains only alphanumeric characters, '-', and '_'
*
* @param str the string to validate
* @return true if the string contains only alphanumeric characters, '-', and '_', or false otherwise
*/
public boolean isAlphanumWithDashes(String str) {
boolean isValid = true;
for (char c : str.toCharArray()) {
if (!Character.isLetterOrDigit(c) && c != '-' && c != '_') {
isValid = false;
break;
}
}
return isValid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import dev.galasa.framework.api.common.ServletError;
import dev.galasa.framework.spi.creds.CredentialsException;
import dev.galasa.framework.spi.creds.ICredentialsService;
import dev.galasa.framework.spi.utils.ITimeService;

import static dev.galasa.framework.api.common.ServletErrorMessage.*;

Expand All @@ -19,11 +20,13 @@ public class Secret {

private String secretId;
private ICredentialsService credentialsService;
private ITimeService timeService;
private ICredentials value;

public Secret(ICredentialsService credentialsService, String secretName) {
public Secret(ICredentialsService credentialsService, String secretName, ITimeService timeService) {
this.secretId = secretName;
this.credentialsService = credentialsService;
this.timeService = timeService;
}

public boolean existsInCredentialsStore() {
Expand All @@ -39,8 +42,10 @@ public void loadValueFromCredentialsStore() throws InternalServletException {
}
}

public void setSecretToCredentialsStore(ICredentials newValue) throws InternalServletException {
public void setSecretToCredentialsStore(ICredentials newValue, String username) throws InternalServletException {
try {
newValue.setLastUpdatedTime(timeService.now());
newValue.setLastUpdatedByUser(username);
credentialsService.setCredentials(secretId, newValue);
} catch (CredentialsException e) {
ServletError error = new ServletError(GAL5077_FAILED_TO_SET_SECRET);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class BaseServletTest {
// "iat": 1516239022
// }
public static final String DUMMY_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0UmVxdWVzdG9yIiwic3ViIjoicmVxdWVzdG9ySWQiLCJuYW1lIjoiSmFjayBTa2VsbGluZ3RvbiIsImlhdCI6MTUxNjIzOTAyMn0.kW1arFknbywrtRrxsLjB2MiXcM6oSgnUrOpuAlE5dhk"; //Dummy JWT
public static final String JWT_USERNAME = "testRequestor";

protected static final GalasaGson gson = new GalasaGson();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2042,7 +2042,20 @@ components:
name:
type: string
description: |
The name of the Galasa Secret to perform a resource action on.
The name that identifies the Galasa Secret.
description:
type: string
description: |
The description to be associated with the Galasa Secret.
lastUpdatedTime:
type: string
format: date-time
description: |
The timestamp at which the Galasa Secret was last updated.
lastUpdatedBy:
type: string
description: |
The ID of the last user that updated the Galasa Secret.
encoding:
type: string
description: |
Expand Down Expand Up @@ -2109,6 +2122,9 @@ components:
name:
type: string
description: The name of the secret to create or update
description:
type: string
description: The description to associate with the secret to create or update
type:
type: string
description: The type of the secret to create or update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@
import dev.galasa.framework.FileSystem;
import dev.galasa.framework.IFileSystem;
import dev.galasa.framework.api.common.BaseServlet;
import dev.galasa.framework.api.common.Environment;
import dev.galasa.framework.api.common.SystemEnvironment;
import dev.galasa.framework.api.common.resources.CPSFacade;
import dev.galasa.framework.api.resources.routes.ResourcesRoute;
import dev.galasa.framework.spi.ConfigurationPropertyStoreException;
import dev.galasa.framework.spi.IFramework;
import dev.galasa.framework.spi.creds.CredentialsException;
import dev.galasa.framework.spi.utils.ITimeService;
import dev.galasa.framework.spi.utils.SystemTimeService;
/*
* Proxy Servlet for the /resources/* endpoints
*/
Expand All @@ -38,6 +42,9 @@ public class ResourcesServlet extends BaseServlet {
protected Log logger = LogFactory.getLog(this.getClass());

protected IFileSystem fileSystem = new FileSystem();

protected ITimeService timeService = new SystemTimeService();
protected Environment env = new SystemEnvironment();

protected IFramework getFramework() {
return this.framework;
Expand All @@ -54,7 +61,7 @@ public void init() throws ServletException {
super.init();

try {
addRoute(new ResourcesRoute(getResponseBuilder(), new CPSFacade(framework), framework.getCredentialsService()));
addRoute(new ResourcesRoute(getResponseBuilder(), new CPSFacade(framework), framework.getCredentialsService(), timeService, env));
} catch (ConfigurationPropertyStoreException | CredentialsException e) {
logger.error("Failed to initialise the Resources servlet", e);
throw new ServletException("Failed to initialise the Resources servlet", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public GalasaPropertyProcessor(CPSFacade cps) {
}

@Override
public List<String> processResource(JsonObject resource, ResourceAction action) throws InternalServletException {
public List<String> processResource(JsonObject resource, ResourceAction action, String username) throws InternalServletException {
List<String> errors = checkGalasaPropertyJsonStructure(resource, action);
try {
if (errors.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
import dev.galasa.ICredentials;
import dev.galasa.framework.api.beans.generated.GalasaSecret;
import dev.galasa.framework.api.beans.generated.GalasaSecretdata;
import dev.galasa.framework.api.beans.generated.GalasaSecretmetadata;
import dev.galasa.framework.api.common.InternalServletException;
import dev.galasa.framework.api.common.ServletError;
import dev.galasa.framework.api.common.resources.BaseResourceValidator;
import dev.galasa.framework.api.common.resources.GalasaSecretType;
import dev.galasa.framework.api.common.resources.ResourceAction;
import dev.galasa.framework.api.common.resources.Secret;
Expand All @@ -36,6 +38,7 @@
import dev.galasa.framework.spi.creds.CredentialsUsernamePassword;
import dev.galasa.framework.spi.creds.CredentialsUsernameToken;
import dev.galasa.framework.spi.creds.ICredentialsService;
import dev.galasa.framework.spi.utils.ITimeService;

/**
* Processor class to handle creating, updating, and deleting GalasaSecret resources
Expand All @@ -47,20 +50,24 @@ public class GalasaSecretProcessor extends AbstractGalasaResourceProcessor imple
private static final List<String> SUPPORTED_ENCODING_SCHEMES = List.of("base64");

private ICredentialsService credentialsService;
private ITimeService timeService;

public GalasaSecretProcessor(ICredentialsService credentialsService) {
private BaseResourceValidator validator = new BaseResourceValidator();

public GalasaSecretProcessor(ICredentialsService credentialsService, ITimeService timeService) {
this.credentialsService = credentialsService;
this.timeService = timeService;
}

@Override
public List<String> processResource(JsonObject resourceJson, ResourceAction action) throws InternalServletException {
public List<String> processResource(JsonObject resourceJson, ResourceAction action, String username) throws InternalServletException {
logger.info("Processing GalasaSecret resource");
List<String> errors = checkGalasaSecretJsonStructure(resourceJson, action);
if (errors.isEmpty()) {
logger.info("GalasaSecret validated successfully");
GalasaSecret galasaSecret = gson.fromJson(resourceJson, GalasaSecret.class);
String credentialsId = galasaSecret.getmetadata().getname();
Secret secret = new Secret(credentialsService, credentialsId);
Secret secret = new Secret(credentialsService, credentialsId, timeService);

if (action == DELETE) {
logger.info("Deleting secret from credentials store");
Expand All @@ -77,12 +84,13 @@ public List<String> processResource(JsonObject resourceJson, ResourceAction acti
throw new InternalServletException(error, HttpServletResponse.SC_NOT_FOUND);
}

GalasaSecretType secretType = GalasaSecretType.getFromString(galasaSecret.getmetadata().gettype().toString());
GalasaSecretmetadata metadata = galasaSecret.getmetadata();
GalasaSecretType secretType = GalasaSecretType.getFromString(metadata.gettype().toString());
GalasaSecretdata decodedData = decodeSecretData(galasaSecret);
ICredentials credentials = getCredentialsFromSecret(secretType, decodedData);
ICredentials credentials = getCredentialsFromSecret(secretType, decodedData, metadata);

logger.info("Setting secret in credentials store");
secret.setSecretToCredentialsStore(credentials);
secret.setSecretToCredentialsStore(credentials, username);
logger.info("Secret set in credentials store OK");
}
logger.info("Processed GalasaSecret resource OK");
Expand Down Expand Up @@ -140,7 +148,11 @@ private List<String> checkGalasaSecretJsonStructure(JsonObject secretJson, Resou
return validationErrors;
}

private ICredentials getCredentialsFromSecret(GalasaSecretType secretType, GalasaSecretdata decodedData) {
private ICredentials getCredentialsFromSecret(
GalasaSecretType secretType,
GalasaSecretdata decodedData,
GalasaSecretmetadata metadata
) {
ICredentials credentials = null;
switch (secretType) {
case USERNAME:
Expand All @@ -158,6 +170,10 @@ private ICredentials getCredentialsFromSecret(GalasaSecretType secretType, Galas
default:
break;
}

if (credentials != null) {
credentials.setDescription(metadata.getdescription());
}
return credentials;
}

Expand All @@ -170,6 +186,15 @@ private void validateSecretMetadata(JsonObject secretJson, List<String> validati
validationErrors.add(new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST).getMessage());
}

// If a description is provided, check that it is valid
if (metadata.has("description")) {
String description = metadata.get("description").getAsString();
if (description.isBlank() || !validator.isLatin1(description)) {
ServletError error = new ServletError(GAL5102_INVALID_SECRET_DESCRIPTION_PROVIDED);
validationErrors.add(new InternalServletException(error, HttpServletResponse.SC_BAD_REQUEST).getMessage());
}
}

// Check if the given secret type is a valid type
if (metadata.has("type")) {
GalasaSecretType secretType = GalasaSecretType.getFromString(metadata.get("type").getAsString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public interface IGalasaResourceProcessor {
*
* @param resourceJson the resource to perform an action on
* @param action the action to perform
* @param username the username of the user performing the action
* @return a list of validation errors encountered when processing the given JSON payload
* @throws InternalServletException if there was an issue processing the resource
*/
List<String> processResource(JsonObject resourceJson, ResourceAction action) throws InternalServletException;
List<String> processResource(JsonObject resourceJson, ResourceAction action, String username) throws InternalServletException;
}
Loading

0 comments on commit be4e991

Please sign in to comment.