Unverified Commit 2d9a588f authored by Benjamin Schmitz's avatar Benjamin Schmitz Committed by GitHub
Browse files

Add Project Access Tokens (#1018)

parent f9baa81b
......@@ -919,6 +919,28 @@ public interface Constants {
}
}
/** Enum to use for specifying the project token scope. */
public enum ProjectAccessTokenScope {
API, READ_API, READ_REGISTRY, WRITE_REGISTRY, READ_REPOSITORY, WRITE_REPOSITORY, CREATE_RUNNER;
private static JacksonJsonEnumHelper<ProjectAccessTokenScope> enumHelper = new JacksonJsonEnumHelper<>(ProjectAccessTokenScope.class);
@JsonCreator
public static ProjectAccessTokenScope forValue(String value) {
return enumHelper.forValue(value);
}
@JsonValue
public String toValue() {
return (enumHelper.toString(this));
}
@Override
public String toString() {
return (enumHelper.toString(this));
}
}
/** Enum for the build_git_strategy of the project instance. */
enum SquashOption {
......
......@@ -51,6 +51,7 @@ import org.gitlab4j.api.models.Issue;
import org.gitlab4j.api.models.Member;
import org.gitlab4j.api.models.Namespace;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectApprovalsConfig;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectFilter;
......@@ -3901,4 +3902,101 @@ public class ProjectApi extends AbstractApi implements Constants {
"projects", getProjectIdOrPath(projectIdOrPath), "remote_mirrors", mirrorId);
return (response.readEntity(RemoteMirror.class));
}
/**
* Lists the projects access tokens for the project.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @return the list of ProjectAccessTokens. The token and lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public List<ProjectAccessToken> listProjectAccessTokens(Object projectIdOrPath) throws GitLabApiException {
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(new GenericType<List<ProjectAccessToken>>() { }));
}
/**
* Gets the specific project access token.
* Only working with GitLab 14.10 and above.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id of the token
* @return the ProjectAccessToken. The token attribute of the object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken getProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
return (response.readEntity(ProjectAccessToken.class));
}
/**
* Creates a new project access token.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param name the name of the token
* @param scopes the scope of the token
* @param expiresAt the date when the token should expire
* @param accessLevel The access level of the token is optional. It can either be 10, 20, 30, 40, or 50.
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt, Long accessLevel) throws GitLabApiException {
GitLabApiForm formData = new GitLabApiForm()
.withParam("name", name, true)
.withParam("expires_at", expiresAt, true)
.withParam("scopes", scopes, true)
.withParam("access_level", accessLevel, false);
Response response = post(Response.Status.CREATED, formData,
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(ProjectAccessToken.class));
}
/**
* Creates a new project access token.
* The default value for the accessLevel is used.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param name the name of the token
* @param scopes the scope of the token
* @param expiresAt the date when the token should expire
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt) throws GitLabApiException {
GitLabApiForm formData = new GitLabApiForm()
.withParam("name", name, true)
.withParam("expires_at", ISO8601.dateOnly(expiresAt), true)
.withParam("scopes", scopes, true)
.withParam("access_level", (Object) null, false);
Response response = post(Response.Status.CREATED, formData,
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(ProjectAccessToken.class));
}
/**
* Rotates the given project access token.
* The token is revoked and a new one which will expire in one week is created to replace it.
* Only working with GitLab 16.0 and above.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id
* @return the newly created ProjectAccessToken.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken rotateProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
Response response = post(Response.Status.OK, (Object) null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId, "rotate");
return (response.readEntity(ProjectAccessToken.class));
}
/**
* Revokes the project access token.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id of the token, which should be revoked
* @throws GitLabApiException if any exception occurs
*/
public void revokeProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
delete(Response.Status.NO_CONTENT, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
}
}
package org.gitlab4j.api.models;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.gitlab4j.api.Constants;
import org.gitlab4j.api.utils.JacksonJson;
import java.util.Date;
import java.util.List;
public class ProjectAccessToken {
private Long userId;
private List<Constants.ProjectAccessTokenScope> scopes;
private String name;
private Date expiresAt;
private Long id;
private Boolean active;
private Date createdAt;
private Boolean revoked;
private Long accessLevel;
private Date lastUsedAt;
private String token;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public List<Constants.ProjectAccessTokenScope> getScopes() {
return scopes;
}
public void setScopes(List<Constants.ProjectAccessTokenScope> scopes) {
this.scopes = scopes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(Date expiredAt) {
this.expiresAt = expiredAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Boolean isActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Boolean isRevoked() {
return revoked;
}
public void setRevoked(Boolean revoked) {
this.revoked = revoked;
}
public Long getAccessLevel() {
return accessLevel;
}
public void setAccessLevel(Long accessLevel) {
this.accessLevel = accessLevel;
}
public Date getLastUsedAt() {
return lastUsedAt;
}
public void setLastUsedAt(Date lastUsedAt) {
this.lastUsedAt = lastUsedAt;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
@Override
public String toString() {
return JacksonJson.toJsonString(this);
}
}
......@@ -168,7 +168,7 @@ public class SetupIntegrationTestExtension implements BeforeAllCallback, Extensi
GitLabApi gitLabApi = GitLabApi.oauth2Login(TEST_HOST_URL, username, password, null, null, true);
// If the tester user doen't exists, create it
// If the tester user doesn't exist, create it
Optional<User> optionalUser = gitLabApi.getUserApi().getOptionalUser(TEST_LOGIN_USERNAME);
if (!optionalUser.isPresent()) {
User userSettings = new User()
......@@ -185,7 +185,7 @@ public class SetupIntegrationTestExtension implements BeforeAllCallback, Extensi
// so use OAUTH2 to get the GitLabApi instance
gitLabApi = GitLabApi.oauth2Login(TEST_HOST_URL, TEST_LOGIN_USERNAME, TEST_LOGIN_PASSWORD, null, null, true);
// Create the sudo as user if it does not exists
// Create the sudo as user if it does not exist
username = HelperUtils.getProperty(SUDO_AS_USERNAME_KEY, "user1");
optionalUser = gitLabApi.getUserApi().getOptionalUser(username);
if (!optionalUser.isPresent()) {
......
......@@ -96,6 +96,7 @@ import org.gitlab4j.api.models.PackageFile;
import org.gitlab4j.api.models.Pipeline;
import org.gitlab4j.api.models.PipelineSchedule;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectApprovalsConfig;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectGroup;
......@@ -783,4 +784,10 @@ public class TestGitLabApiBeans {
List<SearchBlob> searchResults = unmarshalResourceList(SearchBlob.class, "wiki-blobs.json");
assertTrue(compareJson(searchResults, "wiki-blobs.json"));
}
@Test
public void testProjectAccessToken() throws Exception {
ProjectAccessToken token = unmarshalResource(ProjectAccessToken.class, "project-access-token.json");
assertTrue(compareJson(token, "project-access-token.json"));
}
}
......@@ -25,12 +25,15 @@ package org.gitlab4j.api;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
......@@ -43,11 +46,13 @@ import org.gitlab4j.api.models.AccessRequest;
import org.gitlab4j.api.models.Group;
import org.gitlab4j.api.models.Member;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectFilter;
import org.gitlab4j.api.models.User;
import org.gitlab4j.api.models.Variable;
import org.gitlab4j.api.models.Visibility;
import org.gitlab4j.api.utils.ISO8601;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
......@@ -884,4 +889,62 @@ public class TestProjectApi extends AbstractIntegrationTest {
}
}
}
@Test
public void testCreateProjectAccessToken() throws GitLabApiException {
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
final Date expiresAt = Date.from(Instant.now().plusSeconds(48*60*60));
assertNotNull(testProject);
// This does not work with the GitLab version used for the integration tests
// final int size = gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size() + 1;
//
// ProjectAccessToken token = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
//
// assertEquals(size, gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size());
// assertNotNull(token.getCreatedAt());
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
// assertNotNull(token.getId());
// assertEquals(tokenName, token.getName());
// assertFalse(token.isRevoked());
// assertEquals(scopes, token.getScopes());
// assertNotNull(token.getToken());
// assertNotEquals(token.getToken(), "");
// assertNotNull(token.getUserId());
// // unset
// assertNull(token.getLastUsedAt());
//
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
}
@Test
public void testRotateProjectAccessToken() throws GitLabApiException {
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
final Date expiresAt = Date.from(Instant.now().plusSeconds(7*24*60*60));
assertNotNull(testProject);
// This does not work with the GitLab version used for the integration tests
// ProjectAccessToken rotatedToken = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
// ProjectAccessToken token = gitLabApi.getProjectApi().rotateProjectAccessToken(testProject.getId(), rotatedToken.getId());
// rotatedToken = gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), rotatedToken.getId());
//
// assertNotNull(token.getCreatedAt());
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
// assertNotNull(token.getId());
// assertNotEquals(rotatedToken.getId(), token.getId());
// assertEquals(tokenName, token.getName());
// assertFalse(token.isRevoked());
// assertTrue(rotatedToken.isRevoked());
// assertEquals(scopes, token.getScopes());
// assertNotNull(token.getToken());
// assertNotEquals(token.getToken(), "");
// assertNotEquals(rotatedToken.getToken(), token.getToken());
// assertNotNull(token.getUserId());
//
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
}
}
{
"user_id" : 3,
"scopes" : [
"api",
"read_repository"
],
"name" : "Project Access Token Name",
"expires_at" : "2021-01-31T00:00:00Z",
"id" : 10,
"active" : true,
"created_at" : "2021-01-20T22:11:48.151Z",
"revoked" : false,
"access_level": 40,
"last_used_at": "2022-03-15T11:05:42.437Z"
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment