From cd828a57f33cde80f1eb912261a7961e225dc3fd Mon Sep 17 00:00:00 2001 From: James hong Date: Tue, 5 Dec 2023 14:34:55 +1000 Subject: [PATCH 1/2] new api for service account and personal access tokens handler. --- group_serviceaccounts.go | 131 +++++++++++++++++++++++++++++++ group_serviceaccounts_test.go | 140 ++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 group_serviceaccounts.go create mode 100644 group_serviceaccounts_test.go diff --git a/group_serviceaccounts.go b/group_serviceaccounts.go new file mode 100644 index 000000000..1f3e2d6f3 --- /dev/null +++ b/group_serviceaccounts.go @@ -0,0 +1,131 @@ +// +// Copyright 2023, James Hong +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/http" + "time" +) + +// GroupServiceAccount represents a GitLab service account user. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-service-account-user +type GroupServiceAccount struct { + ID int `json:"id"` + Name string `json:"name"` + UserName string `json:"username"` +} + +// CreateServiceAccount create a new service account user for a group. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-service-account-user +func (s *GroupsService) CreateServiceAccount(gid interface{}, options ...RequestOptionFunc) (*GroupServiceAccount, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/service_accounts", PathEscape(group)) + + req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + if err != nil { + return nil, nil, err + } + + sa := new(GroupServiceAccount) + resp, err := s.client.Do(req, sa) + if err != nil { + return nil, resp, err + } + + return sa, resp, nil +} + +// GroupServiceAccountPAT represents a GitLab service account Personal Access Token. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user +type GroupServiceAccountPAT struct { + ID int `json:"id"` + Name string `json:"name"` + Revoked bool `json:"revoked"` + CreatedAt *time.Time `json:"created_at"` + Scopes []string `json:"scopes"` + UserID int `json:"user_id"` + LastUsedAt interface{} `json:"last_used_at"` + Active bool `json:"active"` + ExpiresAt string `json:"expires_at"` + Token string `json:"token"` +} + +// AddServiceAccountsPATOptions represents the available AddServiceAccountsPAT() options. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user +type AddServiceAccountsPATOptions struct { + // Scopes cover the ranges of permission sets. + // https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes + // e.g. api, read_user, read_api, read_repository, read_registry + Scopes []string `json:"scopes,omitempty"` + Name string `json:"name,omitempty"` +} + +// AddServiceAccountsPAT add a new PAT for a service account user for a group. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#add-group-hook +func (s *GroupsService) AddServiceAccountsPAT(gid interface{}, saID int, opt *AddServiceAccountsPATOptions, options ...RequestOptionFunc) (*GroupServiceAccountPAT, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens", PathEscape(group), saID) + + req, err := s.client.NewRequest(http.MethodPost, u, opt, options) + if err != nil { + return nil, nil, err + } + + pat := new(GroupServiceAccountPAT) + resp, err := s.client.Do(req, pat) + if err != nil { + return nil, resp, err + } + + return pat, resp, nil +} + +// RotateServiceAccountsPAT rotate a PAT for a service account user for a group. +// +// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user +func (s *GroupsService) RotateServiceAccountsPAT(gid interface{}, saID, tokenID int, options ...RequestOptionFunc) (*GroupServiceAccountPAT, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens/%d/rotate", PathEscape(group), saID, tokenID) + + req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + if err != nil { + return nil, nil, err + } + + pat := new(GroupServiceAccountPAT) + resp, err := s.client.Do(req, pat) + if err != nil { + return nil, resp, err + } + + return pat, resp, nil +} diff --git a/group_serviceaccounts_test.go b/group_serviceaccounts_test.go new file mode 100644 index 000000000..844d53367 --- /dev/null +++ b/group_serviceaccounts_test.go @@ -0,0 +1,140 @@ +// +// Copyright 2023, James Hong +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package gitlab + +import ( + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestCreateServiceAccount(t *testing.T) { + mux, client := setup(t) + + mux.HandleFunc("/api/v4/groups/1/service_accounts", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + fmt.Fprint(w, ` +{ + "id": 57, + "username": "service_account_group_345_6018816a18e515214e0c34c2b33523fc", + "name": "Service account user" +}`) + }) + + sa, _, err := client.Groups.CreateServiceAccount(1) + if err != nil { + t.Error(err) + } + + want := &GroupServiceAccount{ + ID: 57, + UserName: "service_account_group_345_6018816a18e515214e0c34c2b33523fc", + Name: "Service account user", + } + + if !reflect.DeepEqual(sa, want) { + t.Errorf("CreateServiceAccount returned \ngot:\n%v\nwant:\n%v", Stringify(sa), Stringify(want)) + } +} + +func TestAddServiceAccountsPATServiceAccount(t *testing.T) { + mux, client := setup(t) + + mux.HandleFunc("/api/v4/groups/1/service_accounts/57/personal_access_tokens", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + fmt.Fprint(w, ` +{ + "id":6, + "name":"service_accounts_token", + "revoked":false, + "created_at":"2023-06-13T07:47:13.000Z", + "scopes":["api"], + "user_id":71, + "last_used_at":null, + "active":true, + "expires_at":"2024-06-12", + "token":"random_token" +}`) + }) + datePointer := time.Date(2023, 0o6, 13, 0o7, 47, 13, 0, time.UTC) + saPAT, _, err := client.Groups.AddServiceAccountsPAT(1, 57, &AddServiceAccountsPATOptions{Scopes: []string{"api"}, Name: "service_accounts_token"}) + if err != nil { + t.Error(err) + } + + want := &GroupServiceAccountPAT{ + ID: 6, + Name: "service_accounts_token", + Revoked: false, + CreatedAt: &datePointer, + Scopes: []string{"api"}, + UserID: 71, + LastUsedAt: nil, + Active: true, + ExpiresAt: "2024-06-12", + Token: "random_token", + } + + if !reflect.DeepEqual(saPAT, want) { + t.Errorf("AddServiceAccountsPAT returned \ngot:\n%v\nwant:\n%v", Stringify(saPAT), Stringify(want)) + } +} + +func TestRotateServiceAccountsPATServiceAccount(t *testing.T) { + mux, client := setup(t) + + mux.HandleFunc("/api/v4/groups/1/service_accounts/57/personal_access_tokens/6/rotate", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + fmt.Fprint(w, ` +{ + "id":7, + "name":"service_accounts_token", + "revoked":false, + "created_at":"2023-06-13T07:54:49.000Z", + "scopes":["api"], + "user_id":71, + "last_used_at":null, + "active":true, + "expires_at":"2025-06-20", + "token":"random_token_2" +}`) + }) + datePointer := time.Date(2023, 0o6, 13, 0o7, 54, 49, 0, time.UTC) + saPAT, _, err := client.Groups.RotateServiceAccountsPAT(1, 57, 6) + if err != nil { + t.Error(err) + } + + want := &GroupServiceAccountPAT{ + ID: 7, + Name: "service_accounts_token", + Revoked: false, + CreatedAt: &datePointer, + Scopes: []string{"api"}, + UserID: 71, + LastUsedAt: nil, + Active: true, + ExpiresAt: "2025-06-20", + Token: "random_token_2", + } + + if !reflect.DeepEqual(saPAT, want) { + t.Errorf("RotateServiceAccountsPAT returned \ngot:\n%v\nwant:\n%v", Stringify(saPAT), Stringify(want)) + } +} From 779475121dd506d43a80ca39640f93f8927000ad Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Sat, 9 Dec 2023 17:56:15 +0100 Subject: [PATCH 2/2] Update PR a little --- group_serviceaccounts.go | 59 ++++++++------------- group_serviceaccounts_test.go | 99 +++++++++++++++++++---------------- 2 files changed, 77 insertions(+), 81 deletions(-) diff --git a/group_serviceaccounts.go b/group_serviceaccounts.go index 1f3e2d6f3..4acb0b180 100644 --- a/group_serviceaccounts.go +++ b/group_serviceaccounts.go @@ -19,12 +19,12 @@ package gitlab import ( "fmt" "net/http" - "time" ) // GroupServiceAccount represents a GitLab service account user. // -// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-service-account-user +// GitLab API docs: +// https://docs.gitlab.com/ee/api/groups.html#create-service-account-user type GroupServiceAccount struct { ID int `json:"id"` Name string `json:"name"` @@ -33,7 +33,8 @@ type GroupServiceAccount struct { // CreateServiceAccount create a new service account user for a group. // -// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-service-account-user +// GitLab API docs: +// https://docs.gitlab.com/ee/api/groups.html#create-service-account-user func (s *GroupsService) CreateServiceAccount(gid interface{}, options ...RequestOptionFunc) (*GroupServiceAccount, *Response, error) { group, err := parseID(gid) if err != nil { @@ -55,49 +56,34 @@ func (s *GroupsService) CreateServiceAccount(gid interface{}, options ...Request return sa, resp, nil } -// GroupServiceAccountPAT represents a GitLab service account Personal Access Token. +// CreateServiceAccountPersonalAccessTokenOptions represents the available +// CreateServiceAccountPersonalAccessToken() options. // -// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user -type GroupServiceAccountPAT struct { - ID int `json:"id"` - Name string `json:"name"` - Revoked bool `json:"revoked"` - CreatedAt *time.Time `json:"created_at"` - Scopes []string `json:"scopes"` - UserID int `json:"user_id"` - LastUsedAt interface{} `json:"last_used_at"` - Active bool `json:"active"` - ExpiresAt string `json:"expires_at"` - Token string `json:"token"` -} - -// AddServiceAccountsPATOptions represents the available AddServiceAccountsPAT() options. -// -// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user -type AddServiceAccountsPATOptions struct { - // Scopes cover the ranges of permission sets. - // https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes - // e.g. api, read_user, read_api, read_repository, read_registry - Scopes []string `json:"scopes,omitempty"` - Name string `json:"name,omitempty"` +// GitLab API docs: +// https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user +type CreateServiceAccountPersonalAccessTokenOptions struct { + Scopes *[]string `url:"scopes,omitempty" json:"scopes,omitempty"` + Name *string `url:"name,omitempty" json:"name,omitempty"` } -// AddServiceAccountsPAT add a new PAT for a service account user for a group. +// CreateServiceAccountPersonalAccessToken add a new Personal Access Token for a +// service account user for a group. // -// GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#add-group-hook -func (s *GroupsService) AddServiceAccountsPAT(gid interface{}, saID int, opt *AddServiceAccountsPATOptions, options ...RequestOptionFunc) (*GroupServiceAccountPAT, *Response, error) { +// GitLab API docs: +// https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user +func (s *GroupsService) CreateServiceAccountPersonalAccessToken(gid interface{}, serviceAccount int, opt *CreateServiceAccountPersonalAccessTokenOptions, options ...RequestOptionFunc) (*PersonalAccessToken, *Response, error) { group, err := parseID(gid) if err != nil { return nil, nil, err } - u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens", PathEscape(group), saID) + u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens", PathEscape(group), serviceAccount) req, err := s.client.NewRequest(http.MethodPost, u, opt, options) if err != nil { return nil, nil, err } - pat := new(GroupServiceAccountPAT) + pat := new(PersonalAccessToken) resp, err := s.client.Do(req, pat) if err != nil { return nil, resp, err @@ -106,22 +92,23 @@ func (s *GroupsService) AddServiceAccountsPAT(gid interface{}, saID int, opt *Ad return pat, resp, nil } -// RotateServiceAccountsPAT rotate a PAT for a service account user for a group. +// RotateServiceAccountPersonalAccessToken rotates a Personal Access Token for a +// service account user for a group. // // GitLab API docs: https://docs.gitlab.com/ee/api/groups.html#create-personal-access-token-for-service-account-user -func (s *GroupsService) RotateServiceAccountsPAT(gid interface{}, saID, tokenID int, options ...RequestOptionFunc) (*GroupServiceAccountPAT, *Response, error) { +func (s *GroupsService) RotateServiceAccountPersonalAccessToken(gid interface{}, serviceAccount, token int, options ...RequestOptionFunc) (*PersonalAccessToken, *Response, error) { group, err := parseID(gid) if err != nil { return nil, nil, err } - u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens/%d/rotate", PathEscape(group), saID, tokenID) + u := fmt.Sprintf("groups/%s/service_accounts/%d/personal_access_tokens/%d/rotate", PathEscape(group), serviceAccount, token) req, err := s.client.NewRequest(http.MethodPost, u, nil, options) if err != nil { return nil, nil, err } - pat := new(GroupServiceAccountPAT) + pat := new(PersonalAccessToken) resp, err := s.client.Do(req, pat) if err != nil { return nil, resp, err diff --git a/group_serviceaccounts_test.go b/group_serviceaccounts_test.go index 844d53367..e4793f610 100644 --- a/group_serviceaccounts_test.go +++ b/group_serviceaccounts_test.go @@ -30,11 +30,11 @@ func TestCreateServiceAccount(t *testing.T) { mux.HandleFunc("/api/v4/groups/1/service_accounts", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodPost) fmt.Fprint(w, ` -{ - "id": 57, - "username": "service_account_group_345_6018816a18e515214e0c34c2b33523fc", - "name": "Service account user" -}`) + { + "id": 57, + "username": "service_account_group_345_6018816a18e515214e0c34c2b33523fc", + "name": "Service account user" + }`) }) sa, _, err := client.Groups.CreateServiceAccount(1) @@ -53,88 +53,97 @@ func TestCreateServiceAccount(t *testing.T) { } } -func TestAddServiceAccountsPATServiceAccount(t *testing.T) { +func TestCreateServiceAccountPersonalAccessToken(t *testing.T) { mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/service_accounts/57/personal_access_tokens", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodPost) fmt.Fprint(w, ` -{ - "id":6, - "name":"service_accounts_token", - "revoked":false, - "created_at":"2023-06-13T07:47:13.000Z", - "scopes":["api"], - "user_id":71, - "last_used_at":null, - "active":true, - "expires_at":"2024-06-12", - "token":"random_token" -}`) + { + "id":6, + "name":"service_account_token", + "revoked":false, + "created_at":"2023-06-13T07:47:13.000Z", + "scopes":["api"], + "user_id":71, + "last_used_at":null, + "active":true, + "expires_at":"2024-06-12", + "token":"random_token" + }`) }) - datePointer := time.Date(2023, 0o6, 13, 0o7, 47, 13, 0, time.UTC) - saPAT, _, err := client.Groups.AddServiceAccountsPAT(1, 57, &AddServiceAccountsPATOptions{Scopes: []string{"api"}, Name: "service_accounts_token"}) + options := &CreateServiceAccountPersonalAccessTokenOptions{ + Scopes: Ptr([]string{"api"}), + Name: Ptr("service_account_token"), + } + pat, _, err := client.Groups.CreateServiceAccountPersonalAccessToken(1, 57, options) if err != nil { t.Error(err) } - want := &GroupServiceAccountPAT{ + datePointer := time.Date(2023, 0o6, 13, 0o7, 47, 13, 0, time.UTC) + expiresAt := ISOTime(time.Date(2024, time.June, 12, 0, 0, 0, 0, time.UTC)) + + want := &PersonalAccessToken{ ID: 6, - Name: "service_accounts_token", + Name: "service_account_token", Revoked: false, CreatedAt: &datePointer, Scopes: []string{"api"}, UserID: 71, LastUsedAt: nil, Active: true, - ExpiresAt: "2024-06-12", + ExpiresAt: &expiresAt, Token: "random_token", } - if !reflect.DeepEqual(saPAT, want) { - t.Errorf("AddServiceAccountsPAT returned \ngot:\n%v\nwant:\n%v", Stringify(saPAT), Stringify(want)) + if !reflect.DeepEqual(pat, want) { + t.Errorf("CreateServiceAccountPersonalAccessToken returned \ngot:\n%v\nwant:\n%v", Stringify(pat), Stringify(want)) } } -func TestRotateServiceAccountsPATServiceAccount(t *testing.T) { +func TestRotateServiceAccountPersonalAccessToken(t *testing.T) { mux, client := setup(t) mux.HandleFunc("/api/v4/groups/1/service_accounts/57/personal_access_tokens/6/rotate", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, http.MethodPost) fmt.Fprint(w, ` -{ - "id":7, - "name":"service_accounts_token", - "revoked":false, - "created_at":"2023-06-13T07:54:49.000Z", - "scopes":["api"], - "user_id":71, - "last_used_at":null, - "active":true, - "expires_at":"2025-06-20", - "token":"random_token_2" -}`) + { + "id":7, + "name":"service_account_token", + "revoked":false, + "created_at":"2023-06-13T07:54:49.000Z", + "scopes":["api"], + "user_id":71, + "last_used_at":null, + "active":true, + "expires_at":"2025-06-20", + "token":"random_token_2" + }`) }) - datePointer := time.Date(2023, 0o6, 13, 0o7, 54, 49, 0, time.UTC) - saPAT, _, err := client.Groups.RotateServiceAccountsPAT(1, 57, 6) + + pat, _, err := client.Groups.RotateServiceAccountPersonalAccessToken(1, 57, 6) if err != nil { t.Error(err) } - want := &GroupServiceAccountPAT{ + datePointer := time.Date(2023, 0o6, 13, 0o7, 54, 49, 0, time.UTC) + expiresAt := ISOTime(time.Date(2025, time.June, 20, 0, 0, 0, 0, time.UTC)) + + want := &PersonalAccessToken{ ID: 7, - Name: "service_accounts_token", + Name: "service_account_token", Revoked: false, CreatedAt: &datePointer, Scopes: []string{"api"}, UserID: 71, LastUsedAt: nil, Active: true, - ExpiresAt: "2025-06-20", + ExpiresAt: &expiresAt, Token: "random_token_2", } - if !reflect.DeepEqual(saPAT, want) { - t.Errorf("RotateServiceAccountsPAT returned \ngot:\n%v\nwant:\n%v", Stringify(saPAT), Stringify(want)) + if !reflect.DeepEqual(pat, want) { + t.Errorf("RotateServiceAccountPersonalAccessToken returned \ngot:\n%v\nwant:\n%v", Stringify(pat), Stringify(want)) } }