diff --git a/pom.xml b/pom.xml index 64fce0f73d27348fc43e87b356a840b3be5a8322..e482291316bd7946154507e07ae94233705b12f2 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ <quarkus.platform.version>3.8.3</quarkus.platform.version> <surefire-plugin.version>3.1.2</surefire-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> - <eclipse-api-version>1.0.1</eclipse-api-version> + <eclipse-api-version>1.0.2</eclipse-api-version> <auto-value.version>1.10.4</auto-value.version> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> <sonar.sources>src/main</sonar.sources> diff --git a/spec/openapi.yaml b/spec/openapi.yaml index 0230ec06933f18e797eb7d62c9375bc8e5e4232d..49fe2e295182754e999fe8e6c970a29a5a732fca 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -89,6 +89,18 @@ paths: description: Error while retrieving data /eca/lookup: + parameters: + - name: q + in: query + description: Query string containing either email or username. Email is only valid for logged in committers/project leads + schema: + type: string + - name: email + in: query + deprecated: true + description: Email is only valid for logged in committers/project leads. For removal at the end of 2024. + schema: + type: string get: operationId: getUserStatus tags: diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java deleted file mode 100644 index a956d60141d8b555a799c949dae03202bd8a591a..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java +++ /dev/null @@ -1,61 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.api; - -import java.util.List; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.HeaderParam; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; - -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; - -/** - * Binding interface for the Eclipse Foundation user account API. Runtime - * implementations are automatically generated by - * Quarkus at compile time. As the API deals with sensitive information, - * authentication is required to access this endpoint. - * - * @author Martin Lowe - * - */ -@ApplicationScoped -@RegisterRestClient -@Produces("application/json") -public interface AccountsAPI { - - /** - * Retrieves all user objects that use the given mail address. - * - * @param mail the email address to match against for Eclipse accounts - * @return all matching eclipse accounts - */ - @GET - @Path("/account/profile") - List<EclipseUser> getUsers(@HeaderParam("Authorization") String token, @QueryParam("mail") String mail); - - /** - * Retrieves user objects that matches the given Github username. - * - * @param authBearer authorization header value for validating call - * @param uname username of the Github account to retrieve Eclipse Account - * for - * @return the matching Eclipse account or null - */ - @GET - @Path("/github/profile/{uname}") - EclipseUser getUserByGithubUname(@HeaderParam("Authorization") String token, @PathParam("uname") String uname); - -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/EclipseUser.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/EclipseUser.java deleted file mode 100644 index 04244f8a41cf9631e9d3367760e891c61ca810b8..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/git/eca/api/models/EclipseUser.java +++ /dev/null @@ -1,111 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.git.eca.api.models; - -import jakarta.annotation.Nullable; - -import org.eclipsefoundation.git.eca.model.GitUser; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import com.google.auto.value.AutoValue; - -/** - * Represents a users Eclipse Foundation account - * - * @author Martin Lowe - */ -@AutoValue -@JsonDeserialize(builder = AutoValue_EclipseUser.Builder.class) -public abstract class EclipseUser { - public abstract int getUid(); - - public abstract String getName(); - - public abstract String getMail(); - - public abstract ECA getECA(); - - public abstract boolean getIsCommitter(); - - @Nullable - public abstract String getGithubHandle(); - - @Nullable - @JsonIgnore - public abstract Boolean getIsBot(); - - /** - * Create a bot user stub when there is no real Eclipse account for the bot. - * - * @param user the Git user that was detected to be a bot. - * @return a stubbed Eclipse user bot object. - */ - public static EclipseUser createBotStub(GitUser user) { - return EclipseUser - .builder() - .setUid(0) - .setName(user.getName()) - .setMail(user.getMail()) - .setECA(ECA.builder().build()) - .setIsBot(true) - .build(); - } - - public static Builder builder() { - return new AutoValue_EclipseUser.Builder().setIsCommitter(false).setIsBot(false); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setUid(int id); - - public abstract Builder setName(String name); - - public abstract Builder setMail(String mail); - - public abstract Builder setECA(ECA eca); - - public abstract Builder setIsCommitter(boolean isCommitter); - - public abstract Builder setGithubHandle(@Nullable String githubHandle); - - @JsonIgnore - public abstract Builder setIsBot(@Nullable Boolean isBot); - - public abstract EclipseUser build(); - } - - @AutoValue - @JsonDeserialize(builder = AutoValue_EclipseUser_ECA.Builder.class) - public abstract static class ECA { - public abstract boolean getSigned(); - - public abstract boolean getCanContributeSpecProject(); - - public static Builder builder() { - return new AutoValue_EclipseUser_ECA.Builder().setCanContributeSpecProject(false).setSigned(false); - } - - @AutoValue.Builder - @JsonPOJOBuilder(withPrefix = "set") - public abstract static class Builder { - public abstract Builder setSigned(boolean signed); - - public abstract Builder setCanContributeSpecProject(boolean canContributeSpecProject); - - public abstract ECA build(); - } - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationMessage.java b/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationMessage.java index 1d66043bca17f4d15bcf257d357a76de28b86c1b..7e488301af480d60b89f33f7dbb083898f844b28 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationMessage.java +++ b/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationMessage.java @@ -38,8 +38,9 @@ import jakarta.ws.rs.core.MultivaluedMap; @Table @Entity -public class CommitValidationMessage extends BareNode implements Serializable{ +public class CommitValidationMessage extends BareNode implements Serializable { public static final DtoTable TABLE = new DtoTable(CommitValidationMessage.class, "cvm"); + private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -203,21 +204,21 @@ public class CommitValidationMessage extends BareNode implements Serializable{ // id check String id = params.getFirst(DefaultUrlParameterNames.ID.getName()); if (StringUtils.isNumeric(id)) { - stmt.addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".id = ?", - new Object[] { Long.valueOf(id) })); + stmt.addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".id = ?", new Object[] { Long.valueOf(id) })); } // commit id check String commitId = params.getFirst(GitEcaParameterNames.COMMIT_ID.getName()); if (StringUtils.isNumeric(commitId)) { - stmt.addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".commitId = ?", - new Object[] { Integer.valueOf(commitId) })); + stmt + .addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".commitId = ?", + new Object[] { Integer.valueOf(commitId) })); } // ids check List<String> ids = params.get(DefaultUrlParameterNames.IDS.getName()); if (ids != null && !ids.isEmpty()) { - stmt.addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".id IN ?", - new Object[] { ids.stream().filter(StringUtils::isNumeric).map(Long::valueOf) - .toList() })); + stmt + .addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".id IN ?", + new Object[] { ids.stream().filter(StringUtils::isNumeric).map(Long::valueOf).toList() })); } } return stmt; diff --git a/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationStatus.java b/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationStatus.java index 75558edfb1735813f96d9f7dc9ca6f673b4ae03d..6315af62b52516d83057db6ba1094f6197d948b1 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationStatus.java +++ b/src/main/java/org/eclipsefoundation/git/eca/dto/CommitValidationStatus.java @@ -45,6 +45,7 @@ import jakarta.ws.rs.core.MultivaluedMap; @Entity public class CommitValidationStatus extends BareNode implements Serializable { public static final DtoTable TABLE = new DtoTable(CommitValidationStatus.class, "cvs"); + private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java index 62dc2bf590faca8ffe250ca89c78ce67647b9d5d..e597c300efde1526e027984cea3f838ae74e79de 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/CommonResource.java @@ -13,7 +13,7 @@ package org.eclipsefoundation.git.eca.resource; import java.util.Arrays; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; +import org.eclipsefoundation.efservices.api.models.EfUser; import org.eclipsefoundation.git.eca.dto.GithubWebhookTracking; import org.eclipsefoundation.git.eca.service.UserService; import org.eclipsefoundation.http.model.RequestWrapper; @@ -52,7 +52,7 @@ public abstract class CommonResource { dao.add(new RDBMSQuery<>(wrapper, filters.get(GithubWebhookTracking.class)), Arrays.asList(tracking)); } - EclipseUser getUserForLoggedInAccount() { + EfUser getUserForLoggedInAccount() { if (ident.isAnonymous()) { return null; } diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java index 8416d25bf8c12f4fac46f63d516f40c8ce446e00..cc5be71f5ac31e7c95991d05081ee07089885bdf 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -14,11 +14,12 @@ package org.eclipsefoundation.git.eca.resource; import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.eclipsefoundation.caching.service.CachingService; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; +import org.eclipsefoundation.efservices.api.models.EfUser; +import org.eclipsefoundation.efservices.services.ProfileService; import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; @@ -30,8 +31,8 @@ import org.jboss.resteasy.annotations.jaxrs.QueryParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.quarkus.security.Authenticated; import jakarta.inject.Inject; +import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.NotFoundException; @@ -40,9 +41,10 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; /** - * ECA validation endpoint for Git commits. Will use information from the bots, projects, and accounts API to validate commits passed to + * Eca validation endpoint for Git commits. Will use information from the bots, projects, and accounts API to validate commits passed to * this endpoint. Should be as system agnostic as possible to allow for any service to request validation with less reliance on services * external to the Eclipse foundation. * @@ -62,9 +64,12 @@ public class ValidationResource extends CommonResource { @Inject ValidationService validation; + @Inject + ProfileService profileService; + /** * Consuming a JSON request, this method will validate all passed commits, using the repo URL and the repository provider. These commits - * will be validated to ensure that all users are covered either by an ECA, or are committers on the project. In the case of ECA-only + * will be validated to ensure that all users are covered either by an Eca, or are committers on the project. In the case of Eca-only * contributors, an additional sign off footer is required in the body of the commit. * * @param req the request containing basic data plus the commits to be validated @@ -86,25 +91,68 @@ public class ValidationResource extends CommonResource { } } + /** + * Do a lookup of an email or username to check if the user has an ECA. Does basic checks on query to discover if email or username, + * + * @param email deprecated field, to be removed end of 2024. Contains the email to lookup + * @param query query string for lookup, can contain either an email address or username + * @return + */ @GET @Path("/lookup") - @Authenticated - public Response getUserStatus(@QueryParam("email") String email) { + public Response getUserStatus(@QueryParam("email") String email, @QueryParam("q") String query) { + // really basic check. A username will never have an @, while an email will always have it. + boolean queryLikeEmail = query != null && query.contains("@"); // check that the user has committer level access - EclipseUser loggedInUser = getUserForLoggedInAccount(); - if (loggedInUser == null || !loggedInUser.getIsCommitter()) { - throw new FinalForbiddenException("User must be logged in and have committer level access to use this endpoint."); + EfUser loggedInUser = getUserForLoggedInAccount(); + if (StringUtils.isNotBlank(email)) { + // do the checks to see if the user is missing or has not signed the ECA using the legacy field + handleUserEmailLookup(loggedInUser, email); + } else if (queryLikeEmail) { + // do the checks to see if the user is missing or has not signed the ECA + handleUserEmailLookup(loggedInUser, query); + } else if (StringUtils.isNotBlank(query)) { + // if username is set, look up the user and check if it has an Eca + Optional<EfUser> user = profileService.fetchUserByUsername(query, false); + if (user.isEmpty()) { + throw new NotFoundException(String.format("No user found with username '%s'", TransformationHelper.formatLog(query))); + } + if (!user.get().getEca().getSigned()) { + return Response.status(Status.FORBIDDEN).build(); + } + } else { + throw new BadRequestException("A username or email must be set to look up a user account"); + } + return Response.ok().build(); + } + + /** + * Check permissions on the logged in user, and if permitted, check if the designated email is associated with an EF account with a + * valid ECA. If there is a problem or there is no match, a corresponding error will be thrown. + * + * @param loggedInUser the currently logged in user converted to an EF user object + * @param email the email to search for + */ + private void handleUserEmailLookup(EfUser loggedInUser, String email) { + // check if there is a user logged in, as this always requires authentication + if (ident.isAnonymous()) { + throw new BadRequestException("User must be logged in and have committer level access to search by email"); + } + + // check that user has a project relation as a way of checking user trust + boolean isKnownCommitterOrPL = loggedInUser != null && loggedInUser.getIsCommitter(); + if (!isKnownCommitterOrPL) { + throw new FinalForbiddenException("User must be logged in and have committer level access to search by email"); } // do the lookup of the passed email - EclipseUser user = users.getUser(email); - if (Objects.isNull(user)) { + EfUser user = users.getUser(email); + if (user == null) { throw new NotFoundException(String.format("No user found with mail '%s'", TransformationHelper.formatLog(email))); } - - if (!user.getECA().getSigned()) { + // if the user doesn't have a signed Eca, return an empty 403 + if (!user.getEca().getSigned()) { throw new FinalForbiddenException(""); } - return Response.ok().build(); } /** diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java index 1134f1d9a2ead650a6f9d65fb1521dc639af8f01..d0b13725da1f9d0e99e15e8b4f4537bf0fd24543 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java @@ -13,8 +13,8 @@ package org.eclipsefoundation.git.eca.service; import java.util.List; +import org.eclipsefoundation.efservices.api.models.EfUser; import org.eclipsefoundation.efservices.api.models.Project; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; public interface UserService { @@ -28,7 +28,7 @@ public interface UserService { * @return the Eclipse Account user information if found, or null if there was * an error or no user exists. */ - EclipseUser getUser(String mail); + EfUser getUser(String mail); /** * Retrieves an Eclipse Account user object given the Github username. This is @@ -41,7 +41,7 @@ public interface UserService { * @return the Eclipse Account user information if found, or null if there was * an error or no user exists. */ - EclipseUser getUserByGithubUsername(String username); + EfUser getUserByGithubUsername(String username); /** * Checks the bot API to see whether passed email address is registered to a bot diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java index 27fdb5733dd7290cf481234cee52c2f16d9a9f24..5fae962459e7fe615b8eb58d2a5e4bece72c4b90 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -15,17 +15,18 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; +import java.util.Optional; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.caching.model.CacheWrapper; import org.eclipsefoundation.caching.service.CachingService; +import org.eclipsefoundation.efservices.api.models.EfUser; import org.eclipsefoundation.efservices.api.models.Project; -import org.eclipsefoundation.efservices.services.DrupalTokenService; -import org.eclipsefoundation.git.eca.api.AccountsAPI; +import org.eclipsefoundation.efservices.api.models.UserSearchParams; +import org.eclipsefoundation.efservices.services.ProfileService; import org.eclipsefoundation.git.eca.api.BotsAPI; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; import org.eclipsefoundation.git.eca.config.MailValidationConfig; import org.eclipsefoundation.git.eca.service.UserService; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; @@ -52,19 +53,15 @@ public class CachedUserService implements UserService { @Inject MailValidationConfig config; - // eclipse API rest client interfaces @Inject - @RestClient - AccountsAPI accounts; + CachingService cache; @Inject + ProfileService profile; + + // eclipse API rest client interfaces @RestClient BotsAPI bots; - @Inject - DrupalTokenService oauth; - @Inject - CachingService cache; - // rendered list of regex values List<Pattern> patterns; @@ -75,30 +72,29 @@ public class CachedUserService implements UserService { } @Override - public EclipseUser getUser(String mail) { + public EfUser getUser(String mail) { if (StringUtils.isBlank(mail)) { return null; } - CacheWrapper<EclipseUser> u = cache.get(mail, new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(mail)); - if (u.getData().isPresent()) { + CacheWrapper<EfUser> result = cache.get(mail, new MultivaluedMapImpl<>(), EfUser.class, () -> retrieveUser(mail)); + Optional<EfUser> user = result.getData(); + if (user.isPresent()) { LOGGER.debug("Found user with email {}", mail); - return u.getData().get(); + return user.get(); } LOGGER.debug("Could not find user with email {}", mail); return null; } @Override - public EclipseUser getUserByGithubUsername(String username) { + public EfUser getUserByGithubUsername(String username) { if (StringUtils.isBlank(username)) { return null; } - CacheWrapper<EclipseUser> u = cache - .get("gh:" + username, new MultivaluedMapImpl<>(), EclipseUser.class, - () -> accounts.getUserByGithubUname(getBearerToken(), username)); - if (u.getData().isPresent()) { + Optional<EfUser> user = profile.fetchUserByGhHandle(username, true); + if (user.isPresent()) { LOGGER.debug("Found user with name {}", username); - return u.getData().get(); + return user.get(); } LOGGER.debug("Could not find user with name {}", username); return null; @@ -140,29 +136,28 @@ public class CachedUserService implements UserService { } /** - * Checks for standard and noreply email address matches for a Git user and converts to a Eclipse Foundation account - * object. + * Checks for standard and noreply email address matches for a Git user and converts to a Eclipse Foundation account object. * * @param user the user to attempt account retrieval for. * @return the user account if found by mail, or null if none found. */ - private EclipseUser retrieveUser(String mail) { + private EfUser retrieveUser(String mail) { if (StringUtils.isBlank(mail)) { LOGGER.debug("Blank mail passed, cannot fetch user"); return null; } LOGGER.debug("Getting fresh user for {}", mail); // check for noreply (no reply will never have user account, and fails fast) - EclipseUser eclipseUser = checkForNoReplyUser(mail); - if (eclipseUser != null) { - return eclipseUser; + EfUser noReplyUser = checkForNoReplyUser(mail); + if (noReplyUser != null) { + return noReplyUser; } // standard user check (returns best match) LOGGER.debug("Checking user with mail {}", mail); try { - List<EclipseUser> matches = accounts.getUsers(getBearerToken(), mail); - if (matches != null && !matches.isEmpty()) { - return matches.get(0); + Optional<EfUser> user = profile.performUserSearch(UserSearchParams.builder().setMail(mail).build()); + if (user.isPresent()) { + return user.get(); } } catch (WebApplicationException e) { LOGGER.warn("Could not find user account with mail '{}'", mail); @@ -171,13 +166,13 @@ public class CachedUserService implements UserService { } /** - * Checks git user for no-reply address, and attempts to ratify user through reverse lookup in API service. Currently, - * this service only recognizes Github no-reply addresses as they have a route to be mapped. + * Checks git user for no-reply address, and attempts to ratify user through reverse lookup in API service. Currently, this service only + * recognizes Github no-reply addresses as they have a route to be mapped. * * @param user the Git user account to check for no-reply mail address * @return the Eclipse user if email address is detected no reply and one can be mapped, otherwise null */ - private EclipseUser checkForNoReplyUser(String mail) { + private EfUser checkForNoReplyUser(String mail) { if (StringUtils.isBlank(mail)) { LOGGER.debug("Blank mail passed, cannot fetch user"); return null; @@ -254,15 +249,10 @@ public class CachedUserService implements UserService { } private List<JsonNode> getBots() { - CacheWrapper<List<JsonNode>> allBots = cache.get("allBots", new MultivaluedMapImpl<>(), JsonNode.class, () -> bots.getBots()); - if (allBots.getData().isEmpty()) { - return Collections.emptyList(); - } - return allBots.getData().get(); - } - - private String getBearerToken() { - return "Bearer " + oauth.getToken(); + return cache + .get("allBots", new MultivaluedMapImpl<>(), JsonNode.class, () -> bots.getBots()) + .getData() + .orElse(Collections.emptyList()); } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java index bc5a096c98d0cad912c4d99a827d0a234658df6c..47623a8d31f5b93d044bd85b50d35879f1f48655 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultValidationService.java @@ -11,12 +11,15 @@ **********************************************************************/ package org.eclipsefoundation.git.eca.service.impl; +import java.util.Collections; import java.util.List; import java.util.Optional; import org.apache.commons.lang3.StringUtils; +import org.eclipsefoundation.efservices.api.models.EfUser; +import org.eclipsefoundation.efservices.api.models.EfUser.Country; +import org.eclipsefoundation.efservices.api.models.EfUser.Eca; import org.eclipsefoundation.efservices.api.models.Project; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; import org.eclipsefoundation.git.eca.config.MailValidationConfig; import org.eclipsefoundation.git.eca.dto.CommitValidationStatus; import org.eclipsefoundation.git.eca.helper.CommitHelper; @@ -40,8 +43,8 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; /** - * Default service for validating external requests for ECA validation, as well as storing and retrieving information - * about historic requests. + * Default service for validating external requests for ECA validation, as well as storing and retrieving information about historic + * requests. * * @author Martin Lowe * @@ -60,7 +63,6 @@ public class DefaultValidationService implements ValidationService { @Inject ValidationStatusService statusService; - @Override public ValidationResponse validateIncomingRequest(ValidationRequest req, RequestWrapper wrapper) { // get the projects associated with the current request, if any @@ -106,8 +108,8 @@ public class DefaultValidationService implements ValidationService { } /** - * Process the current request, validating that the passed commit is valid. The author and committers Eclipse Account is - * retrieved, which are then used to check if the current commit is valid for the current project. + * Process the current request, validating that the passed commit is valid. The author and committers Eclipse Account is retrieved, + * which are then used to check if the current commit is valid for the current project. * * @param c the commit to process * @param response the response container @@ -130,13 +132,13 @@ public class DefaultValidationService implements ValidationService { } // retrieve the eclipse account for the author - EclipseUser eclipseAuthor = getIdentifiedUser(author); + EfUser eclipseAuthor = getIdentifiedUser(author); // if the user is a bot, generate a stubbed user if (isAllowedUser(author.getMail()) || users.userIsABot(author.getMail(), filteredProjects)) { response .addMessage(c.getHash(), String.format("Automated user '%1$s' detected for author of commit %2$s", author.getMail(), c.getHash())); - eclipseAuthor = EclipseUser.createBotStub(author); + eclipseAuthor = createBotStub(author); } else if (eclipseAuthor == null) { response .addMessage(c.getHash(), @@ -149,13 +151,13 @@ public class DefaultValidationService implements ValidationService { GitUser committer = c.getCommitter(); // retrieve the eclipse account for the committer - EclipseUser eclipseCommitter = getIdentifiedUser(committer); + EfUser eclipseCommitter = getIdentifiedUser(committer); // check if whitelisted or bot if (isAllowedUser(committer.getMail()) || users.userIsABot(committer.getMail(), filteredProjects)) { response .addMessage(c.getHash(), String.format("Automated user '%1$s' detected for committer of commit %2$s", committer.getMail(), c.getHash())); - eclipseCommitter = EclipseUser.createBotStub(committer); + eclipseCommitter = createBotStub(committer); } else if (eclipseCommitter == null) { response .addMessage(c.getHash(), @@ -174,75 +176,72 @@ public class DefaultValidationService implements ValidationService { } /** - * Validates author access for the current commit. If there are errors, they are recorded in the response for the - * current request to be returned once all validation checks are completed. + * Validates author access for the current commit. If there are errors, they are recorded in the response for the current request to be + * returned once all validation checks are completed. * * @param r the current response object for the request * @param c the commit that is being validated - * @param eclipseUser the user to validate on a branch + * @param EfUser the user to validate on a branch * @param filteredProjects tracked projects for the current request * @param errorCode the error code to display if the user does not have access */ - private void validateUserAccess(ValidationResponse r, Commit c, EclipseUser eclipseUser, List<Project> filteredProjects, + private void validateUserAccess(ValidationResponse r, Commit c, EfUser EfUser, List<Project> filteredProjects, APIStatusCode errorCode) { // call isCommitter inline and pass to partial call - validateUserAccessPartial(r, c, eclipseUser, isCommitter(r, eclipseUser, c.getHash(), filteredProjects), errorCode); + validateUserAccessPartial(r, c, EfUser, isCommitter(r, EfUser, c.getHash(), filteredProjects), errorCode); } /** - * Allows for isCommitter to be called external to this method. This was extracted to ensure that isCommitter isn't - * called twice for the same user when checking committer proxy push rules and committer general access. + * Allows for isCommitter to be called external to this method. This was extracted to ensure that isCommitter isn't called twice for the + * same user when checking committer proxy push rules and committer general access. * * @param r the current response object for the request * @param c the commit that is being validated - * @param eclipseUser the user to validate on a branch + * @param EfUser the user to validate on a branch * @param isCommitter the results of the isCommitter call from this class. * @param errorCode the error code to display if the user does not have access */ - private void validateUserAccessPartial(ValidationResponse r, Commit c, EclipseUser eclipseUser, boolean isCommitter, - APIStatusCode errorCode) { + private void validateUserAccessPartial(ValidationResponse r, Commit c, EfUser EfUser, boolean isCommitter, APIStatusCode errorCode) { String userType = "author"; if (APIStatusCode.ERROR_COMMITTER.equals(errorCode)) { userType = "committer"; } if (isCommitter) { - r - .addMessage(c.getHash(), - String.format("Eclipse user '%s'(%s) is a committer on the project.", eclipseUser.getName(), userType)); + r.addMessage(c.getHash(), String.format("Eclipse user '%s'(%s) is a committer on the project.", EfUser.getName(), userType)); } else { r .addMessage(c.getHash(), - String.format("Eclipse user '%s'(%s) is not a committer on the project.", eclipseUser.getName(), userType)); + String.format("Eclipse user '%s'(%s) is not a committer on the project.", EfUser.getName(), userType)); // check if the author is signed off if not a committer - if (eclipseUser.getECA().getSigned()) { + if (EfUser.getEca().getSigned()) { r .addMessage(c.getHash(), String .format("Eclipse user '%s'(%s) has a current Eclipse Contributor Agreement (ECA) on file.", - eclipseUser.getName(), userType)); + EfUser.getName(), userType)); } else { r .addMessage(c.getHash(), String .format("Eclipse user '%s'(%s) does not have a current Eclipse Contributor Agreement (ECA) on file.\n" - + "If there are multiple commits, please ensure that each author has a ECA.", eclipseUser.getName(), + + "If there are multiple commits, please ensure that each author has a ECA.", EfUser.getName(), userType)); r .addError(c.getHash(), String - .format("An Eclipse Contributor Agreement is required for Eclipse user '%s'(%s).", - eclipseUser.getName(), userType), + .format("An Eclipse Contributor Agreement is required for Eclipse user '%s'(%s).", EfUser.getName(), + userType), errorCode); } } } /** - * Checks whether the given user is a committer on the project. If they are and the project is also a specification for - * a working group, an additional access check is made against the user. + * Checks whether the given user is a committer on the project. If they are and the project is also a specification for a working group, + * an additional access check is made against the user. * * <p> - * Additionally, a check is made to see if the user is a registered bot user for the given project. If they match for - * the given project, they are granted committer-like access to the repository. + * Additionally, a check is made to see if the user is a registered bot user for the given project. If they match for the given project, + * they are granted committer-like access to the repository. * * @param r the current response object for the request * @param user the user to validate on a branch @@ -250,7 +249,7 @@ public class DefaultValidationService implements ValidationService { * @param filteredProjects tracked projects for the current request * @return true if user is considered a committer, false otherwise. */ - private boolean isCommitter(ValidationResponse r, EclipseUser user, String hash, List<Project> filteredProjects) { + private boolean isCommitter(ValidationResponse r, EfUser user, String hash, List<Project> filteredProjects) { // iterate over filtered projects for (Project p : filteredProjects) { LOGGER.debug("Checking project '{}' for user '{}'", p.getName(), user.getName()); @@ -258,7 +257,7 @@ public class DefaultValidationService implements ValidationService { if (p.getCommitters().stream().anyMatch(u -> u.getUsername().equals(user.getName()))) { // check if the current project is a committer project, and if the user can // commit to specs - if (p.getSpecWorkingGroup().isPresent() && !user.getECA().getCanContributeSpecProject()) { + if (p.getSpecWorkingGroup().isPresent() && !user.getEca().getCanContributeSpecProject()) { // set error + update response status r .addError(hash, String @@ -292,18 +291,17 @@ public class DefaultValidationService implements ValidationService { } /** - * Retrieves an Eclipse Account user object given the Git users email address (at minimum). This is facilitated using - * the Eclipse Foundation accounts API, along short lived in-memory caching for performance and some protection against - * duplicate requests. + * Retrieves an Eclipse Account user object given the Git users email address (at minimum). This is facilitated using the Eclipse + * Foundation accounts API, along short lived in-memory caching for performance and some protection against duplicate requests. * * @param user the user to retrieve Eclipse Account information for * @return the Eclipse Account user information if found, or null if there was an error or no user exists. */ - private EclipseUser getIdentifiedUser(GitUser user) { + private EfUser getIdentifiedUser(GitUser user) { // check if the external ID is set, and if so, attempt to look the user up. if (StringUtils.isNotBlank(user.getExternalId())) { // right now this is only supported for Github account lookups, so that will be used - EclipseUser actualUser = users.getUserByGithubUsername(user.getExternalId()); + EfUser actualUser = users.getUserByGithubUsername(user.getExternalId()); // if present, return the user. Otherwise, log and continue processing if (actualUser != null) { return actualUser; @@ -322,7 +320,7 @@ public class DefaultValidationService implements ValidationService { // get the Eclipse account for the user try { // use cache to avoid asking for the same user repeatedly on repeated requests - EclipseUser foundUser = users.getUser(user.getMail()); + EfUser foundUser = users.getUser(user.getMail()); if (foundUser == null) { LOGGER.warn("No users found for mail '{}'", user.getMail()); } @@ -362,4 +360,26 @@ public class DefaultValidationService implements ValidationService { && status.get().getUserMail().equalsIgnoreCase(c.getAuthor().getMail()) && (c.getLastModificationDate() == null || status.get().getLastModified().equals(c.getLastModificationDate())); } + + private EfUser createBotStub(GitUser user) { + return EfUser + .builder() + .setUid("0") + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setName(user.getName()) + .setMail(user.getMail()) + .setEca(Eca.builder().build()) + .setIsBot(true) + .build(); + + } } diff --git a/src/main/resources/templates/simple_fingerprint_ui.html b/src/main/resources/templates/simple_fingerprint_ui.html index 17416fc97c976580647bdd46f8287f4baf36b0ff..db83fc418a772773eedf23413d01e3a5121db44a 100644 --- a/src/main/resources/templates/simple_fingerprint_ui.html +++ b/src/main/resources/templates/simple_fingerprint_ui.html @@ -179,19 +179,23 @@ <section id="block-site-login-eclipse-eca-sle-eca-lookup-tool" class="margin-bottom-30 clearfix"> <h2>ECA Validation Tool</h2> - {#if currentUser and currentUser.isCommitter} <form id="eclipse-eca-lookup-form" accept-charset="UTF-8"> <div class="form-item form-item-input form-type-textfield form-group"> - <input placeholder="Enter email address" class="form-control form-text" type="text" id="email-input" - name="email-input" value="" size="21" maxlength="128" autofill-prediction="UNKNOWN_TYPE"> - <div class="help-block">Enter email address of an Eclipse account.</div> + {#if currentUser and currentUser.isCommitter} + <input placeholder="Enter email address or username" class="form-control form-text" type="text" id="query-input" + name="query-input" value="" size="21" maxlength="128" autofill-prediction="UNKNOWN_TYPE"> + <div class="help-block">Enter email address or username of an Eclipse account.</div> + {#else} + <input placeholder="Enter username" class="form-control form-text" type="text" id="query-input" + name="query-input" value="" size="21" maxlength="128" autofill-prediction="UNKNOWN_TYPE"> + <div class="help-block">Enter username of an Eclipse account.</div> + {/if} </div> <button class="btn-success btn form-submit" type="submit" id="edit-submit">Verify ECA</button> </form> - {#else if currentUser} - <p>Logged in users must have committer level access to use the ECA Validation tool.</p> - {#else} - <p>Please login to use the ECA Validation tool. Please note that only committers are able to use this tool.</p> + {#if currentUser == null} + <hr /> + <p>If you are a committer or project lead and wish to query for users by their email address, please login.</p> <a class="btn btn-primary" href="/git/login?redirect={redirectUri}">Login</a> {/if} </section> @@ -260,30 +264,27 @@ // don't submit the form as we will handle it w/ ajax e.preventDefault(); // grab the constants from the form and perform a check - const inputVal = $(e.target).find('#email-input').val(); + const inputVal = $(e.target).find('#query-input').val(); const $submitButton = $(e.target).find('button'); // disable the button so that requests won't be spammed $submitButton.attr("disabled", "disabled"); // use ajax to check the ECA status of the user - $.ajax({ - url: `/git/eca/lookup`, - data: { - email: inputVal - }, - success: function (data) { - toast(`There is a valid ECA on file for <em>${escapeHTML(inputVal)}</em>`, 'success'); - }, - error: function (xhr) { - console.log(xhr.status); - if (xhr.status == '403') { - toast(`There is no valid ECA on file for <em>${escapeHTML(inputVal)}</em>`, 'danger'); - } else { + fetch('/git/eca/lookup?' + new URLSearchParams({ + q: inputVal, + }).toString()).then(d => { + if (d.status === 200) { + toast(`There is a valid ECA on file for <em>${escapeHTML(inputVal)}</em>`, 'success'); + } else if (d.status === 400) { + // we pull out the message from the error to give user some context + d.json().then(error => { + toast(`<p>Could not lookup <em>${escapeHTML(inputVal)}</em></p><p>Reason: ${error.message}</p>`, 'warning'); + }); + } else if (d.status === 404) { toast(`No Eclipse Foundation account found for <em>${escapeHTML(inputVal)}</em>`, 'warning'); + } else { + toast(`Error encountered while checking for an ECA on file for <em>${escapeHTML(inputVal)}</em>`, 'danger'); } - }, - complete: function () { $submitButton.removeAttr("disabled"); - } }); }); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index 1a2931c882d26e72ca5e6842f944697cffc87c34..b2b2bd95da42816e47a542e41d563635057bbeab 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -59,7 +59,8 @@ import jakarta.ws.rs.core.Response.Status; @QuarkusTest class ValidationResourceTest { public static final String ECA_BASE_URL = "/eca"; - public static final String LOOKUP_URL = ECA_BASE_URL + "/lookup?email={param}"; + public static final String LOOKUP_URL = ECA_BASE_URL + "/lookup?q={param}"; + public static final String LOOKUP_LEGACY_URL = ECA_BASE_URL + "/lookup?email={param}"; public static final String STATUS_URL = ECA_BASE_URL + "/status/{fingerprint}"; public static final String STATUS_UI_URL = STATUS_URL + "/ui"; @@ -116,16 +117,36 @@ class ValidationResourceTest { /* * LOOKUP CASES */ + // by email public static final EndpointTestCase LOOKUP_SUCCESS_CASE = TestCaseHelper .buildSuccessCase(LOOKUP_URL, new String[] { "slom@eclipse-foundation.org" }, ""); public static final EndpointTestCase LOOKUP_ANONYMOUS_CASE = TestCaseHelper .prepareTestCase(LOOKUP_URL, new String[] { "slom@eclipse-foundation.org" }, "") - .setStatusCode(Status.UNAUTHORIZED.getStatusCode()) + .setStatusCode(Status.BAD_REQUEST.getStatusCode()) .build(); public static final EndpointTestCase LOOKUP_FORBIDDEN_CASE = TestCaseHelper .buildForbiddenCase(LOOKUP_URL, new String[] { "newbie@important.co" }, ""); public static final EndpointTestCase LOOKUP_NOT_FOUND_CASE = TestCaseHelper .buildNotFoundCase(LOOKUP_URL, new String[] { "dummy@fake.co" }, ""); + // by username + public static final EndpointTestCase LOOKUP_USERNAME_SUCCESS_CASE = TestCaseHelper + .buildSuccessCase(LOOKUP_URL, new String[] { "barshall_blathers" }, ""); + public static final EndpointTestCase LOOKUP_USERNAME_FORBIDDEN_CASE = TestCaseHelper + .buildForbiddenCase(LOOKUP_URL, new String[] { "newbieAnon" }, ""); + public static final EndpointTestCase LOOKUP_USERNAME_NOT_FOUND_CASE = TestCaseHelper + .buildNotFoundCase(LOOKUP_URL, new String[] { "dummy11" }, ""); + + // lookup cases with email param + public static final EndpointTestCase LOOKUP_SUCCESS_EMAIL_PARAM_CASE = TestCaseHelper + .buildSuccessCase(LOOKUP_LEGACY_URL, new String[] { "slom@eclipse-foundation.org" }, ""); + public static final EndpointTestCase LOOKUP_ANONYMOUS_EMAIL_PARAM_CASE = TestCaseHelper + .prepareTestCase(LOOKUP_LEGACY_URL, new String[] { "slom@eclipse-foundation.org" }, "") + .setStatusCode(Status.UNAUTHORIZED.getStatusCode()) + .build(); + public static final EndpointTestCase LOOKUP_FORBIDDEN_EMAIL_PARAM_CASE = TestCaseHelper + .buildForbiddenCase(LOOKUP_LEGACY_URL, new String[] { "newbie@important.co" }, ""); + public static final EndpointTestCase LOOKUP_NOT_FOUND_EMAIL_PARAM_CASE = TestCaseHelper + .buildNotFoundCase(LOOKUP_LEGACY_URL, new String[] { "dummy@fake.co" }, ""); @Inject CachingService cs; @@ -880,10 +901,34 @@ class ValidationResourceTest { EndpointTestBuilder.from(LOOKUP_ANONYMOUS_CASE).run(); } + // by username + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_username_userNotFound() { + EndpointTestBuilder.from(LOOKUP_USERNAME_NOT_FOUND_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_username_userNoECA() { + EndpointTestBuilder.from(LOOKUP_USERNAME_FORBIDDEN_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_username_userSuccess() { + EndpointTestBuilder.from(LOOKUP_USERNAME_SUCCESS_CASE).run(); + } + + // by email @Test @TestSecurity(user = "newbieAnon", roles = AuthHelper.DEFAULT_ROLE) @OidcSecurity(claims = { @Claim(key = "email", value = "newbie@important.co") }) void validateUserLookup_failure_nonCommitter() { + // committer required for email lookup EndpointTestBuilder.from(LOOKUP_FORBIDDEN_CASE).run(); } @@ -905,7 +950,37 @@ class ValidationResourceTest { @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) void validateUserLookup_userSuccess() { - EndpointTestBuilder.from(LOOKUP_NOT_FOUND_CASE).run(); + EndpointTestBuilder.from(LOOKUP_SUCCESS_CASE).run(); + } + + // legacy lookup cases using email param + + @Test + @TestSecurity(user = "newbieAnon", roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "newbie@important.co") }) + void validateUserLookup_legacy_failure_nonCommitter() { + EndpointTestBuilder.from(LOOKUP_FORBIDDEN_EMAIL_PARAM_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_legacy_userNotFound() { + EndpointTestBuilder.from(LOOKUP_NOT_FOUND_EMAIL_PARAM_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_legacy_userNoECA() { + EndpointTestBuilder.from(LOOKUP_FORBIDDEN_EMAIL_PARAM_CASE).run(); + } + + @Test + @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = AuthHelper.DEFAULT_ROLE) + @OidcSecurity(claims = { @Claim(key = "email", value = "opearson@important.co") }) + void validateUserLookup_legacy_userSuccess() { + EndpointTestBuilder.from(LOOKUP_SUCCESS_EMAIL_PARAM_CASE).run(); } // The default commit for most users. Used for most user tests diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java index 66f40486b2060c287c9ed89cd897a7dc7d0548cb..d52a5f18f3bb2f93e2d1d9cce93277e61cd62eaa 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/service/impl/CachedUserServiceTest.java @@ -16,16 +16,15 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import jakarta.inject.Inject; - +import org.eclipsefoundation.efservices.api.models.EfUser; import org.eclipsefoundation.efservices.api.models.Project; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; import org.eclipsefoundation.git.eca.helper.ProjectHelper; import org.eclipsefoundation.git.eca.service.UserService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; /** * Tests user service impl using test stub data available from API stubs. While not a perfect test as there is no auth, @@ -44,7 +43,7 @@ class CachedUserServiceTest { @Test void getUser_success() { - EclipseUser u = users.getUser("grunt@important.co"); + EfUser u = users.getUser("grunt@important.co"); // assert that this is the user we expect and that it exists Assertions.assertNotNull(u); Assertions.assertEquals("grunter", u.getName()); @@ -52,7 +51,7 @@ class CachedUserServiceTest { @Test void getUser_noReplyGH_success() { - EclipseUser u = users.getUser("123456789+grunter2@users.noreply.github.com"); + EfUser u = users.getUser("123456789+grunter2@users.noreply.github.com"); // assert that this is the user we expect and that it exists Assertions.assertNotNull(u); Assertions.assertEquals("grunter", u.getName()); @@ -76,7 +75,7 @@ class CachedUserServiceTest { @Test void getUserByGithubUsername_success() { - EclipseUser u = users.getUserByGithubUsername("grunter2"); + EfUser u = users.getUserByGithubUsername("grunter2"); // assert that this is the user we expect and that it exists Assertions.assertNotNull(u); Assertions.assertEquals("grunt@important.co", u.getMail()); diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java index 69ca77bc6bf89cef43eb904107d78546b6075dbf..7e233c43908ab64794d211fa4d8ace737d1fe994 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java @@ -11,19 +11,21 @@ **********************************************************************/ package org.eclipsefoundation.git.eca.test.api; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; - -import jakarta.enterprise.context.ApplicationScoped; +import java.util.function.Predicate; import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipsefoundation.git.eca.api.AccountsAPI; -import org.eclipsefoundation.git.eca.api.models.EclipseUser; -import org.eclipsefoundation.git.eca.api.models.EclipseUser.ECA; +import org.eclipsefoundation.efservices.api.ProfileAPI; +import org.eclipsefoundation.efservices.api.models.EfUser; +import org.eclipsefoundation.efservices.api.models.EfUser.Country; +import org.eclipsefoundation.efservices.api.models.EfUser.Eca; +import org.eclipsefoundation.efservices.api.models.UserSearchParams; import io.quarkus.test.Mock; +import jakarta.enterprise.context.ApplicationScoped; /** * Simple stub for accounts API. Allows for easy testing of users that don't really exist upstream, and so that we don't need a real auth @@ -35,103 +37,185 @@ import io.quarkus.test.Mock; @Mock @RestClient @ApplicationScoped -public class MockAccountsAPI implements AccountsAPI { +public class MockAccountsAPI implements ProfileAPI { - private Map<String, EclipseUser> users; + private Map<String, EfUser> users; public MockAccountsAPI() { int id = 0; this.users = new HashMap<>(); users .put("newbie@important.co", - EclipseUser + EfUser .builder() .setIsCommitter(false) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("newbie@important.co") .setName("newbieAnon") - .setECA(ECA.builder().build()) + .setEca(Eca.builder().build()) .build()); users .put("slom@eclipse-foundation.org", - EclipseUser + EfUser .builder() .setIsCommitter(false) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("slom@eclipse-foundation.org") .setName("barshall_blathers") - .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(true).setSigned(true).build()) .build()); users .put("tester@eclipse-foundation.org", - EclipseUser + EfUser .builder() .setIsCommitter(false) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("tester@eclipse-foundation.org") .setName("mctesterson") - .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(false).setSigned(true).build()) .build()); users .put("code.wiz@important.co", - EclipseUser + EfUser .builder() .setIsCommitter(true) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("code.wiz@important.co") .setName("da_wizz") - .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(true).setSigned(true).build()) .setGithubHandle("wiz_in_da_hub") .build()); users .put("grunt@important.co", - EclipseUser + EfUser .builder() .setIsCommitter(true) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("grunt@important.co") .setName("grunter") - .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(false).setSigned(true).build()) .setGithubHandle("grunter2") .build()); users .put("paper.pusher@important.co", - EclipseUser + EfUser .builder() .setIsCommitter(false) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("paper.pusher@important.co") .setName("sumAnalyst") - .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(false).setSigned(true).build()) .build()); users .put("opearson@important.co", - EclipseUser + EfUser .builder() .setIsCommitter(true) - .setUid(id++) + .setPicture("") + .setFirstName("") + .setLastName("") + .setFullName("") + .setPublisherAgreements(Collections.emptyMap()) + .setTwitterHandle("") + .setJobTitle("") + .setWebsite("") + .setCountry(Country.builder().build()) + .setInterests(Collections.emptyList()) + .setUid(Integer.toString(id++)) .setMail("opearson@important.co") .setName("opearson") - .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .setEca(Eca.builder().setCanContributeSpecProject(false).setSigned(true).build()) .setGithubHandle("opearson") - .setIsCommitter(true) .build()); } @Override - public List<EclipseUser> getUsers(String token, String mail) { - return Arrays.asList(users.get(mail)); + public EfUser getUserByEfUsername(String token, String uname) { + return users.values().stream().filter(usernamePredicate(uname)).findFirst().orElseGet(() -> null); } @Override - public EclipseUser getUserByGithubUname(String token, String uname) { - // assumes github id is same as uname for purposes of lookup (simplifies fetch logic) + public EfUser getUserByGithubHandle(String token, String ghHandle) { return users .values() .stream() - .filter(u -> u.getGithubHandle() != null && u.getGithubHandle().equals(uname)) + .filter(u -> u.getGithubHandle() != null && u.getGithubHandle().equals(ghHandle)) .findFirst() .orElseGet(() -> null); } + @Override + public List<EfUser> getUsers(String token, UserSearchParams params) { + return users + .values() + .stream() + .filter(usernamePredicate(params.getName())) + .filter(u -> params.getMail() == null || u.getMail().equals(params.getMail())) + .filter(u -> params.getUid() == null || u.getUid().equals(params.getUid())) + .toList(); + } + + private Predicate<EfUser> usernamePredicate(String target) { + return u -> target == null || u.getName().equals(target); + } }