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

Commit

Permalink
Refactored extensions and fixed tests
Browse files Browse the repository at this point in the history
Signed-off-by: Aashir Siddiqui <[email protected]>
  • Loading branch information
aashir21 committed Oct 24, 2024
1 parent f8179cb commit a0678b2
Show file tree
Hide file tree
Showing 13 changed files with 849 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.commons.logging.Log;
import org.apache.http.HttpStatus;
Expand All @@ -22,6 +21,8 @@
import org.apache.http.impl.client.CloseableHttpClient;

import dev.galasa.auth.couchdb.internal.beans.AuthDBNameViewDesign;
import dev.galasa.auth.couchdb.internal.beans.FrontEndClient;
import dev.galasa.auth.couchdb.internal.beans.UserDoc;
import dev.galasa.extensions.common.api.HttpClientFactory;
import dev.galasa.extensions.common.api.LogFactory;
import dev.galasa.extensions.common.couchdb.CouchdbException;
Expand All @@ -32,11 +33,11 @@
import dev.galasa.extensions.common.api.HttpRequestFactory;
import dev.galasa.framework.spi.auth.IInternalAuthToken;
import dev.galasa.framework.spi.auth.IInternalUser;
import dev.galasa.framework.spi.auth.UserDoc;
import dev.galasa.framework.spi.auth.IUser;
import dev.galasa.framework.spi.auth.IAuthStore;
import dev.galasa.framework.spi.auth.IFrontEndClient;
import dev.galasa.framework.spi.utils.ITimeService;
import dev.galasa.framework.spi.auth.AuthStoreException;
import dev.galasa.framework.spi.auth.FrontendClient;

/**
* When CouchDB is being used to store user-related information, including
Expand All @@ -59,7 +60,7 @@ public class CouchdbAuthStore extends CouchdbStore implements IAuthStore {
public static final String COUCHDB_AUTH_TYPE = "Basic";

public static final String TOKENS_DB_VIEW_NAME = "loginId-view";
public static final String USERS_DB_VIEW_NAME = "users-loginId-view";
public static final String USERS_DB_VIEW_NAME = "loginId-view";

private Log logger;
private ITimeService timeService;
Expand Down Expand Up @@ -173,11 +174,11 @@ private IInternalAuthToken getAuthTokenFromDocument(String documentId) throws Co
}

@Override
public List<UserDoc> getAllUsers() throws AuthStoreException {
public List<IUser> getAllUsers() throws AuthStoreException {
logger.info("Retrieving all users from couchdb");

List<ViewRow> userDocuments = new ArrayList<>();
List<UserDoc> users = new ArrayList<>();
List<IUser> users = new ArrayList<>();

try {
userDocuments = getAllDocsFromDatabase(USERS_DATABASE_NAME);
Expand All @@ -198,123 +199,144 @@ public List<UserDoc> getAllUsers() throws AuthStoreException {

@Override
public void createUser(String loginId, String clientName) throws AuthStoreException {
String userJson = gson.toJson(new UserDoc(loginId, List.of(new FrontendClient(clientName, Instant.now()))));

FrontEndClient client = new FrontEndClient();

client.setClientName(clientName);
client.setLastLogin(Instant.now());

String userJson = gson.toJson(new UserDoc(loginId, List.of(client)));

try {
createDocument(USERS_DATABASE_NAME, userJson);
} catch (CouchdbException e) {
String errorMessage = ERROR_FAILED_TO_CREATE_USER_DOCUMENT.getMessage(e.getMessage());
throw new AuthStoreException(errorMessage, e);
}

return;
}

@Override
public UserDoc getUserByLoginId(String loginId) throws AuthStoreException {
public IUser getUserByLoginId(String loginId) throws AuthStoreException {
logger.info("Retrieving user by loginId from CouchDB");
List<ViewRow> userDocument = new ArrayList<>();
List<UserDoc> users = new ArrayList<>();

UserDoc user = null;
String revNumber = null;
List<ViewRow> userDocument;
IUser user = null;

try {

// Fetch documents matching the loginId
userDocument = getAllDocsByLoginId(USERS_DATABASE_NAME, loginId, USERS_DB_VIEW_NAME);

// Build up a list of all the tokens using the document IDs
for (ViewRow row : userDocument) {
users.add(getUserFromDocument(row.id));
// Since loginIds are unique, there should be only one document.
if (!userDocument.isEmpty()) {
ViewRow row = userDocument.get(0); // Get the first entry since loginId is unique

// Fetch the user document from the CouchDB using the ID from the row
UserDoc fetchedUser = getUserFromDocument(row.id);
logger.info("Fetched user: " + fetchedUser);

if (row.value != null) {
AuthDBNameViewDesign nameViewDesign = gson.fromJson(gson.toJson(row.value),
AuthDBNameViewDesign.class);
revNumber = nameViewDesign._rev;
AuthDBNameViewDesign.class);
fetchedUser.setVersion(nameViewDesign._rev); // Set the version from the CouchDB rev
}

// Assign fetchedUser to the user variable
user = fetchedUser;
}

logger.info("User retrieved from CouchDB OK");

} catch (CouchdbException e) {
String errorMessage = ERROR_FAILED_TO_RETRIEVE_USERS.getMessage(e.getMessage());
throw new AuthStoreException(errorMessage, e);
}

// Always going to return one entry, as loginIds are unique.
// Hence, we can take out the first index
if (!users.isEmpty()) {

user = users.get(0);
user.setUserNumber(userDocument.get(0).id);
user.setVersion(revNumber);

}

return user;

}

@Override
public void updateUserClientActivity(String loginId, String clientName) throws AuthStoreException {
public IUser updateUser(IUser user) throws AuthStoreException {
// Take a clone of the user we are passed, so we can guarantee we are using our bean which
// serialises to the correct format.
UserDoc userDoc = new UserDoc(user);
updateUserDoc(httpClient, storeUri, 0, userDoc);
return userDoc;
}

UserDoc user = getUserByLoginId(loginId);
private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user)
throws AuthStoreException {

List<FrontendClient> clients = user.getClients();
validateUserDoc(user);

Optional<FrontendClient> clientOptional = clients.stream()
.filter(client -> client.getClientName().equals(clientName))
.findFirst();
HttpEntityEnclosingRequestBase request = buildUpdateUserDocRequest(user, couchdbUri);

if (clientOptional.isPresent()) {
FrontendClient client = clientOptional.get();
client.setLastLoggedIn(Instant.now());
} else {
// User has used a new client.
FrontendClient newClient = new FrontendClient(clientName, Instant.now());
clients.add(newClient);
}
PutPostResponse putResponse = sendPutRequestToCouchDb(request);

validateCouchdbResponseJson(user.getUserNumber(), putResponse);

// The version of the document in couchdb has updated, so lets update our version in the doc we were sent,
// so that the same document could be used for another update.
user.setVersion(putResponse.rev);
}

private PutPostResponse sendPutRequestToCouchDb(HttpEntityEnclosingRequestBase request) throws AuthStoreException {
PutPostResponse putResponse;
try {
updateUserDoc(httpClient, storeUri, 0, user);
String entity = sendHttpRequest(request, HttpStatus.SC_CREATED);
putResponse = gson.fromJson(entity, PutPostResponse.class);

} catch (CouchdbException e) {
e.printStackTrace();
throw new AuthStoreException(e);
String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT.getMessage(e.getMessage());
throw new AuthStoreException(errorMessage,e);
}
return putResponse;
}

private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user)
throws CouchdbException {
private HttpEntityEnclosingRequestBase buildUpdateUserDocRequest(UserDoc user, URI couchdbUri) {
HttpEntityEnclosingRequestBase request;

String jsonStructure = gson.toJson(user);

HttpEntityEnclosingRequestBase request;

if (user.getUserNumber() == null) {
request = httpRequestFactory.getHttpPostRequest(couchdbUri + "/" + USERS_DATABASE_NAME);
} else {
request = httpRequestFactory
.getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber());
request.setHeader("If-Match", user.getVersion());

logger.info("Rev is: " + user.getVersion());
}

request = httpRequestFactory
.getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber());
request.setHeader("If-Match", user.getVersion());

request.setEntity(new StringEntity(jsonStructure, StandardCharsets.UTF_8));
return request;
}

try {
String entity = sendHttpRequest(request, HttpStatus.SC_CREATED);
PutPostResponse putPostResponse = gson.fromJson(entity, PutPostResponse.class);

if (putPostResponse.id == null || putPostResponse.rev == null) {
throw new CouchdbException("Unable to store the user structure - Invalid JSON response");
}
private void validateCouchdbResponseJson(String expectedUserNumber , PutPostResponse putResponse) throws AuthStoreException {
if (putResponse.id == null || putResponse.rev == null) {
String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INVALID_RESP.getMessage();
throw new AuthStoreException(errorMessage);
}
if (!expectedUserNumber.equals(putResponse.id)) {
String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_MISMATCH_DOC_ID.getMessage();
throw new AuthStoreException(errorMessage);
}
}

user.setUserNumber(putPostResponse.id);
user.setVersion(putPostResponse.rev);
private void validateUserDoc(UserDoc user) throws AuthStoreException {
if (user.getUserNumber() == null) {
String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_NUMBER.getMessage();
throw new AuthStoreException(errorMessage);
}

if (user.getVersion() == null) {
String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_VERSION.getMessage();
throw new AuthStoreException(errorMessage);
}
}

} catch (CouchdbException e) {
throw new CouchdbException(e);
}
@Override
public void deleteUser(IUser user) throws AuthStoreException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'deleteUser'");
}

@Override
public IFrontEndClient createClient(String clientName) {
return new FrontEndClient(clientName, timeService.now());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void checkTokensDesignDocument(CloseableHttpClient httpClient, URI couchd

AuthDBNameViewDesign tableDesign = parseTokenDesignFromJson(docJson);

boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign);
boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign, dbName);

if (isDesignUpdated) {
updateTokenDesignDocument(httpClient, couchdbUri, attempts, tableDesign, dbName);
Expand All @@ -118,23 +118,27 @@ private AuthDBNameViewDesign parseTokenDesignFromJson(String docJson) throws Cou
return tableDesign;
}

private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesign) {
private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesign, String dbName) {
boolean isUpdated = false;

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

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

if (tableDesign.views.loginIdView.map == null
|| !DB_TABLE_TOKENS_DESIGN.equals(tableDesign.views.loginIdView.map)) {
if (tableDesign.views.loginIdView.map == null) {
isUpdated = true;
tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN;
if(dbName.equals("galasa_tokens")){
tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN;
}
else{
tableDesign.views.loginIdView.map = DB_TABLE_USERS_DESIGN;
}
}

if (tableDesign.language == null || !tableDesign.language.equals("javascript")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
public class AuthDBNameViewDesign {
public String _rev;
public String _id;
public TokenDBViews views;
public AuthStoreDBViews views;
public String language;

public AuthDBNameViewDesign(String _rev, String _id, TokenDBViews views, String language) {
public AuthDBNameViewDesign(String _rev, String _id, AuthStoreDBViews views, String language) {
this._rev = _rev;
this._id = _id;
this.views = views;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
*/
package dev.galasa.auth.couchdb.internal.beans;

public class TokenDBLoginView {
public class AuthStoreDBLoginView {
public String map;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import com.google.gson.annotations.SerializedName;

public class TokenDBViews {
public class AuthStoreDBViews {
@SerializedName("loginId-view")
public TokenDBLoginView loginIdView;
public AuthStoreDBLoginView loginIdView;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dev.galasa.auth.couchdb.internal.beans;

import com.google.gson.annotations.SerializedName;

import dev.galasa.framework.spi.auth.IFrontEndClient;
import java.time.Instant;

public class FrontEndClient implements IFrontEndClient{

@SerializedName("client-name")
private String clientName;

@SerializedName("last-login")
private Instant lastLogin;

// No-arg constructor
public FrontEndClient() {}

// Parameterized constructor
public FrontEndClient(String clientName, Instant lastLoggedIn) {
this.clientName = clientName;
this.lastLogin = lastLoggedIn;
}

public FrontEndClient(IFrontEndClient fClient){
this.clientName = fClient.getClientName();
this.lastLogin = fClient.getLastLogin();
}

// Getter and Setter for lastLoggedIn
public Instant getLastLogin() {
return lastLogin;
}

@Override
public void setLastLogin(Instant lastLoginTime) {
this.lastLogin = lastLoginTime;
}

// Getter and Setter for clientName
public String getClientName() {
return clientName;
}

public void setClientName(String clientName) {
this.clientName = clientName;
}

// toString method to display client details
@Override
public String toString() {
return "Client [clientName=" + clientName + ", lastLoggedIn=" + lastLogin + "]";
}

}
Loading

0 comments on commit a0678b2

Please sign in to comment.