Skip to content

Commit

Permalink
Merge pull request #269 from galasa-dev/Iss1976
Browse files Browse the repository at this point in the history
Implemented get user's access token by loginId
  • Loading branch information
aashir21 authored Sep 13, 2024
2 parents 3df6dcc + d6f63f8 commit 8914b41
Show file tree
Hide file tree
Showing 29 changed files with 1,421 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public CouchdbAuthStore(
this.logger = logFactory.getLog(getClass());
this.timeService = timeService;

validator.checkCouchdbDatabaseIsValid(this.storeUri, this.httpClient, this.httpRequestFactory);
validator.checkCouchdbDatabaseIsValid(this.storeUri, this.httpClient, this.httpRequestFactory, timeService);
}

@Override
Expand All @@ -83,6 +83,28 @@ public List<IInternalAuthToken> getTokens() throws AuthStoreException {
return tokens;
}

public List<IInternalAuthToken> getTokensByLoginId(String loginId) throws AuthStoreException {
logger.info("Retrieving tokens from CouchDB");
List<ViewRow> tokenDocuments = new ArrayList<>();
List<IInternalAuthToken> tokens = new ArrayList<>();

try {
// Get all of the documents in the tokens database
tokenDocuments = getAllDocsByLoginId(TOKENS_DATABASE_NAME, loginId);

// Build up a list of all the tokens using the document IDs
for (ViewRow row : tokenDocuments) {
tokens.add(getAuthTokenFromDocument(row.id));
}

logger.info("Tokens retrieved from CouchDB OK");
} catch (CouchdbException e) {
String errorMessage = ERROR_FAILED_TO_RETRIEVE_TOKENS.getMessage(e.getMessage());
throw new AuthStoreException(errorMessage, e);
}
return tokens;
}

@Override
public void shutdown() throws AuthStoreException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,205 @@
import java.net.URI;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import com.google.gson.JsonSyntaxException;


import dev.galasa.extensions.common.api.LogFactory;

import dev.galasa.extensions.common.couchdb.CouchdbBaseValidator;
import dev.galasa.extensions.common.couchdb.CouchdbClashingUpdateException;
import dev.galasa.extensions.common.couchdb.CouchdbException;
import dev.galasa.extensions.common.couchdb.RetryableCouchdbUpdateOperationProcessor;
import dev.galasa.auth.couchdb.internal.beans.*;
import dev.galasa.extensions.common.api.HttpRequestFactory;
import dev.galasa.framework.spi.utils.GalasaGson;
import dev.galasa.framework.spi.utils.ITimeService;

import static dev.galasa.auth.couchdb.internal.Errors.*;

public class CouchdbAuthStoreValidator extends CouchdbBaseValidator {

private final Log logger = LogFactory.getLog(getClass());
private final Log logger ;
private final GalasaGson gson = new GalasaGson();
private final LogFactory logFactory;

// A couchDB view, it gets all the access tokens of a the user based on the loginId provided.
public static final String DB_TABLE_TOKENS_DESIGN = "function (doc) { if (doc.owner && doc.owner.loginId) {emit(doc.owner.loginId, doc); } }";

public CouchdbAuthStoreValidator() {
this(new LogFactory(){
@Override
public Log getLog(Class<?> clazz) {
return org.apache.commons.logging.LogFactory.getLog(clazz);
}
});
}

public CouchdbAuthStoreValidator(LogFactory logFactory) {
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
}

@Override
public void checkCouchdbDatabaseIsValid(URI couchdbUri, CloseableHttpClient httpClient, HttpRequestFactory httpRequestFactory) throws CouchdbException {
public void checkCouchdbDatabaseIsValid(
URI couchdbUri,
CloseableHttpClient httpClient,
HttpRequestFactory httpRequestFactory,
ITimeService timeService
) throws CouchdbException {

// Perform the base CouchDB checks
super.checkCouchdbDatabaseIsValid(couchdbUri, httpClient, httpRequestFactory);
super.checkCouchdbDatabaseIsValid(couchdbUri, httpClient, httpRequestFactory, timeService);

RetryableCouchdbUpdateOperationProcessor retryProcessor = new RetryableCouchdbUpdateOperationProcessor(timeService, this.logFactory);

retryProcessor.retryCouchDbUpdateOperation(
()->{ tryToCheckAndUpdateCouchDBTokenView(couchdbUri, httpClient, httpRequestFactory);
});

logger.debug("Auth Store CouchDB at " + couchdbUri.toString() + " validated");
}

private void tryToCheckAndUpdateCouchDBTokenView(URI couchdbUri, CloseableHttpClient httpClient,
HttpRequestFactory httpRequestFactory) throws CouchdbException {

validateDatabasePresent(couchdbUri, CouchdbAuthStore.TOKENS_DATABASE_NAME);
checkTokensDesignDocument(httpClient, couchdbUri, 1);
}

logger.debug("Auth Store CouchDB at " + couchdbUri.toString() + " validated");
public void checkTokensDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts)
throws CouchdbException {

// Get the design document from couchdb
String docJson = getTokenDesignDocument(httpClient, couchdbUri, attempts);

TokensDBNameViewDesign tableDesign = parseTokenDesignFromJson(docJson);

boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign);

if (isDesignUpdated) {
updateTokenDesignDocument(httpClient, couchdbUri, attempts, tableDesign);
}
}

private TokensDBNameViewDesign parseTokenDesignFromJson(String docJson) throws CouchdbException {
TokensDBNameViewDesign tableDesign;
try {
tableDesign = gson.fromJson(docJson, TokensDBNameViewDesign.class);
} catch (JsonSyntaxException ex) {
throw new CouchdbException(ERROR_FAILED_TO_PARSE_COUCHDB_DESIGN_DOC.getMessage(ex.getMessage()), ex);
}

if (tableDesign == null) {
tableDesign = new TokensDBNameViewDesign();
}
return tableDesign;
}

private boolean updateDesignDocToDesiredDesignDoc(TokensDBNameViewDesign tableDesign) {
boolean isUpdated = false;

if (tableDesign.views == null) {
isUpdated = true;
tableDesign.views = new TokenDBViews();
}

if (tableDesign.views.loginIdView == null) {
isUpdated = true;
tableDesign.views.loginIdView = new TokenDBLoginView();
}

if (tableDesign.views.loginIdView.map == null
|| !DB_TABLE_TOKENS_DESIGN.equals(tableDesign.views.loginIdView.map)) {
isUpdated = true;
tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN;
}

if (tableDesign.language == null || !tableDesign.language.equals("javascript")) {
isUpdated = true;
tableDesign.language = "javascript";
}

return isUpdated;
}

private String getTokenDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts)
throws CouchdbException {
HttpRequestFactory requestFactory = super.getRequestFactory();
HttpGet httpGet = requestFactory.getHttpGetRequest(couchdbUri + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME +"/_design/docs");

String docJson = null;
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {

StatusLine statusLine = response.getStatusLine();

docJson = EntityUtils.toString(response.getEntity());
if (statusLine.getStatusCode() != HttpStatus.SC_OK
&& statusLine.getStatusCode() != HttpStatus.SC_NOT_FOUND) {
throw new CouchdbException(
"Validation failed of database galasa_tokens design document - " + statusLine.toString());
}
if (statusLine.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
docJson = "{}";
}

return docJson;

} catch (CouchdbException e) {
throw e;
} catch (Exception e) {
throw new CouchdbException("Validation failed", e);
}
}

private void updateTokenDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts,
TokensDBNameViewDesign tokenViewDesign) throws CouchdbException {
HttpRequestFactory requestFactory = super.getRequestFactory();

logger.info("Updating the galasa_tokens design document");

HttpEntity entity = new StringEntity(gson.toJson(tokenViewDesign), ContentType.APPLICATION_JSON);

HttpPut httpPut = requestFactory.getHttpPutRequest(couchdbUri + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME +"/_design/docs");
httpPut.setEntity(entity);

if (tokenViewDesign._rev != null) {
httpPut.addHeader("ETaq", "\"" + tokenViewDesign._rev + "\"");
}

try (CloseableHttpResponse response = httpClient.execute(httpPut)) {
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();

EntityUtils.consumeQuietly(response.getEntity());

if (statusCode == HttpStatus.SC_CONFLICT) {
// Someone possibly updated the document while we were thinking about it.
// It was probably another instance of this exact code.
throw new CouchdbClashingUpdateException(ERROR_FAILED_TO_UPDATE_COUCHDB_DESING_DOC_CONFLICT.toString());
}

if (statusCode != HttpStatus.SC_CREATED) {

throw new CouchdbException(
"Update of galasa_tokens design document failed on CouchDB server - " + statusLine.toString());
}

} catch (CouchdbException e) {
throw e;
} catch (Exception e) {
throw new CouchdbException("Update of galasa_tokens design document failed", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/

package dev.galasa.auth.couchdb.internal;


import java.text.MessageFormat;

public enum Errors {

ERROR_FAILED_TO_PARSE_COUCHDB_DESIGN_DOC (7500,
"GAL7500E: The Galasa auth extension could not check that couchdb has the correct definition for the dababase in which access tokens are stored."+
"The design of the database could not be parsed. Please report this error to your Galasa system administrator. Detailed cause of this problem: {}"),
ERROR_FAILED_TO_UPDATE_COUCHDB_DESING_DOC_CONFLICT (7501,
"GAL7501E: The Galasa auth extension could not upgrade the definition of the couchdb database in which access tokens are stored."+
"The design of the database could not be updated due to clashing updates. Please report this error to your Galasa system administrator."),
;

private String template;
private int expectedParameterCount;
private Errors(int ordinal, String template ) {
this.template = template ;
this.expectedParameterCount = this.template.split("[{]").length-1;
}

public String getMessage() {
String msg ;
int actualParameterCount = 0;

if (actualParameterCount!= this.expectedParameterCount) {
msg = getWrongNumberOfParametersErrorMessage(actualParameterCount,expectedParameterCount);
} else {
msg = this.template;
}

return msg;
}

public String getMessage(Object o1) {

String msg ;
int actualParameterCount = 1;

if (actualParameterCount!= this.expectedParameterCount) {
msg = getWrongNumberOfParametersErrorMessage(actualParameterCount,expectedParameterCount);
} else {
msg = MessageFormat.format(this.template,o1);
}

return msg;
}

public String getMessage(Object o1, Object o2) {

String msg ;
int actualParameterCount = 2;

if (actualParameterCount!= this.expectedParameterCount) {
msg = getWrongNumberOfParametersErrorMessage(actualParameterCount,expectedParameterCount);
} else {
msg = MessageFormat.format(this.template,o1,o2);
}

return msg;
}

public String getMessage(Object o1, Object o2, Object o3) {

String msg ;
int actualParameterCount = 3;

if (actualParameterCount!= this.expectedParameterCount) {
msg = getWrongNumberOfParametersErrorMessage(actualParameterCount,expectedParameterCount);
} else {
msg = MessageFormat.format(this.template,o1,o2,o3);
}

return msg;
}

private String getWrongNumberOfParametersErrorMessage(int actualParameterCount,int expectedParameterCount) {
String template = "Failed to render message template. Not the expected number of parameters. Got ''{0}''. Expected ''{1}''";
String msg = MessageFormat.format(template,actualParameterCount, this.expectedParameterCount);
return msg ;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.auth.couchdb.internal.beans;

public class TokenDBLoginView {
public String map;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.auth.couchdb.internal.beans;

import com.google.gson.annotations.SerializedName;

public class TokenDBViews {
@SerializedName("loginId-view")
public TokenDBLoginView loginIdView;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright contributors to the Galasa project
*
* SPDX-License-Identifier: EPL-2.0
*/
package dev.galasa.auth.couchdb.internal.beans;

//{
// "_id": "_design/docs",
// "_rev": "3-xxxxxxxxxxx9c9072dyy",
// "views": {
// "loginId-view": {
// "map": "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"
// }
// },
// "language": "javascript"
// }
public class TokensDBNameViewDesign {
public String _rev;
public String _id;
public TokenDBViews views;
public String language;
}

Loading

0 comments on commit 8914b41

Please sign in to comment.