diff --git a/src/main/java/org/gitlab4j/api/Constants.java b/src/main/java/org/gitlab4j/api/Constants.java index f15510919ffac735d830d695c8e90f8b2818ab11..9b60ef4fe633a5e847667530b14f3a9ada4c56d8 100644 --- a/src/main/java/org/gitlab4j/api/Constants.java +++ b/src/main/java/org/gitlab4j/api/Constants.java @@ -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 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 { diff --git a/src/main/java/org/gitlab4j/api/ProjectApi.java b/src/main/java/org/gitlab4j/api/ProjectApi.java index 3dff0e76fe64f4c56b8f839011fe8eec20908980..aee4f6115b1441a5e342da7243510bc34cf55659 100644 --- a/src/main/java/org/gitlab4j/api/ProjectApi.java +++ b/src/main/java/org/gitlab4j/api/ProjectApi.java @@ -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 listProjectAccessTokens(Object projectIdOrPath) throws GitLabApiException { + Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens"); + return (response.readEntity(new GenericType>() { })); + } + + /** + * 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 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 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); + } + } diff --git a/src/main/java/org/gitlab4j/api/models/ProjectAccessToken.java b/src/main/java/org/gitlab4j/api/models/ProjectAccessToken.java new file mode 100644 index 0000000000000000000000000000000000000000..1acae21e66dab73e46869c7e69efc8c764ea6aac --- /dev/null +++ b/src/main/java/org/gitlab4j/api/models/ProjectAccessToken.java @@ -0,0 +1,116 @@ +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 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 getScopes() { + return scopes; + } + + public void setScopes(List 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); + } +} diff --git a/src/test/java/org/gitlab4j/api/SetupIntegrationTestExtension.java b/src/test/java/org/gitlab4j/api/SetupIntegrationTestExtension.java index ef967ed12b1b2d74539265304a2a4b74368a67a7..40a78d19c666c724d54ff3268fee72627a3f0739 100644 --- a/src/test/java/org/gitlab4j/api/SetupIntegrationTestExtension.java +++ b/src/test/java/org/gitlab4j/api/SetupIntegrationTestExtension.java @@ -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 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()) { diff --git a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java index 57bb730c9fe89ee050edd3c168b641f7422cfbed..fb94c0ec11a5fa484034d112155b0cbe7dd3c5c7 100644 --- a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java +++ b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java @@ -97,6 +97,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; @@ -791,4 +792,10 @@ public class TestGitLabApiBeans { List 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")); + } } diff --git a/src/test/java/org/gitlab4j/api/TestProjectApi.java b/src/test/java/org/gitlab4j/api/TestProjectApi.java index 68069041a9dc542204e82c98ede72649a8622da9..d2159d15b2fce164f8489284ff0e7e26f2c0d9a3 100644 --- a/src/test/java/org/gitlab4j/api/TestProjectApi.java +++ b/src/test/java/org/gitlab4j/api/TestProjectApi.java @@ -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 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 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()); + } } diff --git a/src/test/resources/org/gitlab4j/api/project-access-token.json b/src/test/resources/org/gitlab4j/api/project-access-token.json new file mode 100644 index 0000000000000000000000000000000000000000..a2af09abb7cc38d80c1a8f8bf79a3f89e08fed04 --- /dev/null +++ b/src/test/resources/org/gitlab4j/api/project-access-token.json @@ -0,0 +1,15 @@ +{ + "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" +}