Commit ef43281e authored by Martin Lowe's avatar Martin Lowe 🇨🇦 Committed by Martin Lowe
Browse files

Change bot detection logic and add tests around bot detection in API



Added tests for all 3 supported platforms (Github, Gitlab, and Gerrit)
around bot detection. Changed bot detection policy to check if user is
any bot, regardless of project commit is for. This allows for bots to
push in untracked projects when using strict mode as well.
Signed-off-by: Martin Lowe's avatarMartin Lowe <martin.lowe@eclipse-foundation.org>
parent 13b319eb
/*******************************************************************************
/**
* 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
******************************************************************************/
*
* <p>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/
*
* <p>SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents a bot user in the Eclipse API.
*
* @author Martin Lowe
*
* @author Martin Lowe
*/
public class BotUser {
private String id;
private String projectId;
private String username;
private String email;
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @return the projectId
*/
public String getProjectId() {
return projectId;
}
/**
* @param projectId the projectId to set
*/
public void setProjectId(String projectId) {
this.projectId = projectId;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/**
* @return the email
*/
public String getEmail() {
return email;
}
/**
* @param email the email to set
*/
public void setEmail(String email) {
this.email = email;
}
private String id;
private String projectId;
private String username;
private String email;
@JsonProperty("github.com")
private SiteSpecificBot githubBot;
@JsonProperty("gitlab.eclipse.org")
private SiteSpecificBot gitlabBot;
/** @return the id */
public String getId() {
return id;
}
/** @param id the id to set */
public void setId(String id) {
this.id = id;
}
/** @return the projectId */
public String getProjectId() {
return projectId;
}
/** @param projectId the projectId to set */
public void setProjectId(String projectId) {
this.projectId = projectId;
}
/** @return the username */
public String getUsername() {
return username;
}
/** @param username the username to set */
public void setUsername(String username) {
this.username = username;
}
/** @return the email */
public String getEmail() {
return email;
}
/**
* @return the githubBot
*/
public SiteSpecificBot getGithubBot() {
return githubBot;}
/**
* @param githubBot the githubBot to set
*/
public void setGithubBot(SiteSpecificBot githubBot) {
this.githubBot = githubBot;}
/**
* @return the gitlabBot
*/
public SiteSpecificBot getGitlabBot() {
return gitlabBot;}
/**
* @param gitlabBot the gitlabBot to set
*/
public void setGitlabBot(SiteSpecificBot gitlabBot) {
this.gitlabBot = gitlabBot;}
/** @param email the email to set */
public void setEmail(String email) {
this.email = email;
}
@Override public String toString(){StringBuilder builder=new StringBuilder();builder.append("BotUser [id=");builder.append(id);builder.append(", projectId=");builder.append(projectId);builder.append(", username=");builder.append(username);builder.append(", email=");builder.append(email);builder.append(", githubBot=");builder.append(githubBot);builder.append(", gitlabBot=");builder.append(gitlabBot);builder.append("]");return builder.toString();}
public static class SiteSpecificBot {
private String username;
private String email;
/** @return the username */
public String getUsername() {
return username;
}
/** @param username the username to set */
public void setUsername(String username) {
this.username = username;
}
/** @return the email */
public String getEmail() {
return email;
}
/** @param email the email to set */
public void setEmail(String email) {
this.email = email;
}
}
}
/*******************************************************************************
/**
* 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
******************************************************************************/
*
* <p>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/
*
* <p>SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.git.eca.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
/**
* Represents a users Eclipse Foundation account
*
* @author Martin Lowe
*
* @author Martin Lowe
*/
public class EclipseUser {
private int uid;
private String name;
private String mail;
private ECA eca;
private boolean isCommitter;
/**
* @return the id
*/
public int getId() {
return uid;
}
/**
* @param id the id to set
*/
public void setId(int uid) {
this.uid = uid;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the mail
*/
public String getMail() {
return mail;
}
/**
* @param mail the mail to set
*/
public void setMail(String mail) {
this.mail = mail;
}
/**
* @return the eca
*/
public ECA getEca() {
return eca;
}
/**
* @param eca the eca to set
*/
public void setEca(ECA eca) {
this.eca = eca;
}
/**
* @return the isCommitter
*/
public boolean isCommitter() {
return isCommitter;
}
/**
* @param isCommitter the isCommitter to set
*/
public void setCommitter(boolean isCommitter) {
this.isCommitter = isCommitter;
}
/**
* ECA for Eclipse accounts, representing whether users have signed the Eclipse
* Committer Agreement to enable contribution.
*/
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public static class ECA {
private boolean signed;
private boolean canContributeSpecProject;
public ECA() {
this(false, false);
}
public ECA(boolean signed, boolean canContributeSpecProject) {
this.signed = signed;
this.canContributeSpecProject = canContributeSpecProject;
}
/**
* @return the signed
*/
public boolean isSigned() {
return signed;
}
/**
* @param signed the signed to set
*/
public void setSigned(boolean signed) {
this.signed = signed;
}
/**
* @return the canContributeSpecProject
*/
public boolean isCanContributeSpecProject() {
return canContributeSpecProject;
}
/**
* @param canContributeSpecProject the canContributeSpecProject to set
*/
public void setCanContributeSpecProject(boolean canContributeSpecProject) {
this.canContributeSpecProject = canContributeSpecProject;
}
}
private int uid;
private String name;
private String mail;
private ECA eca;
private boolean isCommitter;
// this field is internal for tracking bot stubs
@JsonIgnore private boolean isBot;
/**
* 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) {
EclipseUser stub = new EclipseUser();
stub.setName(user.getName());
stub.setMail(user.getMail());
stub.setEca(new ECA());
stub.setBot(true);
return stub;
}
/** @return the id */
public int getId() {
return uid;
}
/** @param id the id to set */
public void setId(int uid) {
this.uid = uid;
}
/** @return the name */
public String getName() {
return name;
}
/** @param name the name to set */
public void setName(String name) {
this.name = name;
}
/** @return the mail */
public String getMail() {
return mail;
}
/** @param mail the mail to set */
public void setMail(String mail) {
this.mail = mail;
}
/** @return the eca */
public ECA getEca() {
return eca;
}
/** @param eca the eca to set */
public void setEca(ECA eca) {
this.eca = eca;
}
/** @return the isCommitter */
public boolean isCommitter() {
return isCommitter;
}
/** @param isCommitter the isCommitter to set */
public void setCommitter(boolean isCommitter) {
this.isCommitter = isCommitter;
}
/** @return the isBot */
public boolean isBot() {
return isBot;
}
/** @param isBot the isBot to set */
private void setBot(boolean isBot) {
this.isBot = isBot;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("EclipseUser [uid=");
builder.append(uid);
builder.append(", name=");
builder.append(name);
builder.append(", mail=");
builder.append(mail);
builder.append(", eca=");
builder.append(eca);
builder.append(", isCommitter=");
builder.append(isCommitter);
builder.append(", isBot=");
builder.append(isBot);
builder.append("]");
return builder.toString();
}
/**
* ECA for Eclipse accounts, representing whether users have signed the Eclipse Committer
* Agreement to enable contribution.
*/
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public static class ECA {
private boolean signed;
private boolean canContributeSpecProject;
public ECA() {
this(false, false);
}
public ECA(boolean signed, boolean canContributeSpecProject) {
this.signed = signed;
this.canContributeSpecProject = canContributeSpecProject;
}
/** @return the signed */
public boolean isSigned() {
return signed;
}
/** @param signed the signed to set */
public void setSigned(boolean signed) {
this.signed = signed;
}
/** @return the canContributeSpecProject */
public boolean isCanContributeSpecProject() {
return canContributeSpecProject;
}
/** @param canContributeSpecProject the canContributeSpecProject to set */
public void setCanContributeSpecProject(boolean canContributeSpecProject) {
this.canContributeSpecProject = canContributeSpecProject;
}
}
}
/**
* ***************************************************************************** Copyright (C) 2020
* Eclipse Foundation
* Copyright (C) 2020 Eclipse Foundation
*
* <p>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/
*
* <p>SPDX-License-Identifier: EPL-2.0
* ****************************************************************************
*/
package org.eclipsefoundation.git.eca.resource;
......@@ -104,7 +102,7 @@ public class ValidationResource {
r.setTrackedProject(!filteredProjects.isEmpty());
for (Commit c : req.getCommits()) {
// process the request, capturing if we should continue processing
boolean continueProcessing = processCommit(c, r, filteredProjects);
boolean continueProcessing = processCommit(c, r, filteredProjects, req.getProvider());
// if there is a reason to stop processing, break the loop
if (!continueProcessing) {
break;
......@@ -129,7 +127,10 @@ public class ValidationResource {
* @return true if we should continue processing, false otherwise.
*/
private boolean processCommit(
Commit c, ValidationResponse response, List<Project> filteredProjects) {
Commit c,
ValidationResponse response,
List<Project> filteredProjects,
ProviderType provider) {
// ensure the commit is valid, and has required fields
if (!CommitHelper.validateCommit(c)) {
addError(
......@@ -161,34 +162,44 @@ public class ValidationResource {
// retrieve the eclipse account for the author
EclipseUser eclipseAuthor = getIdentifiedUser(author);
if (eclipseAuthor == null) {
addMessage(
response,
String.format(
"Could not find an Eclipse user with mail '%1$s' for author of commit %2$s",
committer.getMail(), c.getHash()),
c.getHash());
addError(response, "Author must have an Eclipse Account", c.getHash());
return true;
// if the user is a bot, generate a stubbed user
if (!userIsABot(author.getMail(), provider)) {
addMessage(
response,
String.format(
"Could not find an Eclipse user with mail '%1$s' for author of commit %2$s",
committer.getMail(), c.getHash()),
c.getHash());
addError(response, "Author must have an Eclipse Account", c.getHash());
return true;
}
// set the Eclipse author as the basic committer w\ bot flag to pass information forward
eclipseAuthor = EclipseUser.createBotStub(author);
}
// retrieve the eclipse account for the committer
EclipseUser eclipseCommitter = getIdentifiedUser(committer);
if (eclipseCommitter == null) {
addMessage(
response,
String.format(
"Could not find an Eclipse user with mail '%1$s' for committer of commit %2$s",
committer.getMail(), c.getHash()),
c.getHash());
addError(response, "Committing user must have an Eclipse Account", c.getHash());
return true;
if (!userIsABot(committer.getMail(), provider)) {
addMessage(
response,
String.format(
"Could not find an Eclipse user with mail '%1$s' for committer of commit %2$s",
committer.getMail(), c.getHash()),
c.getHash());
addError(response, "Committing user must have an Eclipse Account", c.getHash());
return true;
}
// set the Eclipse committer as the basic committer w\ bot flag to pass information forward
eclipseCommitter = EclipseUser.createBotStub(committer);
}
// validate author access to the current repo
validateAuthorAccess(response, c, eclipseAuthor, filteredProjects);
validateAuthorAccess(response, c, eclipseAuthor, filteredProjects, provider);
// only committers can push on behalf of other users
if (response.isTrackedProject() && !eclipseAuthor.equals(eclipseCommitter)
&& !isCommitter(response, eclipseCommitter, c.getHash(), filteredProjects)) {
if (response.isTrackedProject()
&& !eclipseAuthor.equals(eclipseCommitter)
&& !isCommitter(response, eclipseCommitter, c.getHash(), filteredProjects, provider)) {
addMessage(response, "You are not a project committer.", c.getHash());
addMessage(response, "Only project committers can push on behalf of others.", c.getHash());
addError(response, "You must be a committer to push on behalf of others.", c.getHash());
......@@ -204,11 +215,16 @@ public class ValidationResource {
* @param c the commit that is being validated
* @param eclipseAuthor the user to validate on a branch
* @param filteredProjects tracked projects for the current request
* @param provider the provider set for the current request
*/
private void validateAuthorAccess(
ValidationResponse r, Commit c, EclipseUser eclipseAuthor, List<Project> filteredProjects) {
ValidationResponse r,
Commit c,
EclipseUser eclipseAuthor,
List<Project> filteredProjects,
ProviderType provider) {
// check if the author matches to an eclipse user and is a committer
if (isCommitter(r, eclipseAuthor, c.getHash(), filteredProjects)) {