mirror of https://github.com/harness/drone.git
Add endpoint for allowing admins to force rotate a user's token (#3272)
* Add endpoint for allowing admins to force rotate a user's token * Finishing a missed test for user not found, and finished test for db update errorpull/3279/head
parent
5792a1bd86
commit
c7587fd3f0
|
@ -340,6 +340,7 @@ func (s Server) Handler() http.Handler {
|
|||
r.Post("/", users.HandleCreate(s.Users, s.Userz, s.Webhook))
|
||||
r.Get("/{user}", users.HandleFind(s.Users))
|
||||
r.Patch("/{user}", users.HandleUpdate(s.Users, s.Transferer))
|
||||
r.Post("/{user}/token/rotate", users.HandleTokenRotation(s.Users))
|
||||
r.Delete("/{user}", users.HandleDelete(s.Users, s.Transferer, s.Webhook))
|
||||
r.Get("/{user}/repos", users.HandleRepoList(s.Users, s.Repos))
|
||||
})
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2019 Drone IO, Inc.
|
||||
//
|
||||
// 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 users
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/drone/drone/core"
|
||||
"github.com/drone/drone/handler/api/render"
|
||||
"github.com/drone/drone/logger"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type userWithMessage struct {
|
||||
*core.User
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// HandleToken returns an http.HandlerFunc that writes json-encoded
|
||||
// account information to the http response body with the user token.
|
||||
func HandleTokenRotation(users core.UserStore) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
login := chi.URLParam(r, "user")
|
||||
user, err := users.FindLogin(r.Context(), login)
|
||||
if err != nil {
|
||||
render.NotFound(w, err)
|
||||
logger.FromRequest(r).WithError(err).
|
||||
Debugln("api: cannot find user")
|
||||
return
|
||||
}
|
||||
user.Hash = uniuri.NewLen(32)
|
||||
if err := users.Update(r.Context(), user); err != nil {
|
||||
render.InternalError(w, err)
|
||||
return
|
||||
}
|
||||
render.JSON(w, &userWithMessage{user, "Token rotated successfully."}, 200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Drone Non-Commercial License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/core"
|
||||
"github.com/drone/drone/handler/api/errors"
|
||||
"github.com/drone/drone/mock"
|
||||
"github.com/go-chi/chi"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
)
|
||||
|
||||
// The purpose of this test is to make sure admins can rotate someone
|
||||
// else's token.
|
||||
func TestTokenRotate(t *testing.T) {
|
||||
controller := gomock.NewController(t)
|
||||
defer controller.Finish()
|
||||
startingHash := "MjAxOC0wOC0xMVQxNTo1ODowN1o"
|
||||
mockUser := &core.User{
|
||||
ID: 1,
|
||||
Login: "octocat",
|
||||
Hash: startingHash,
|
||||
}
|
||||
|
||||
c := new(chi.Context)
|
||||
c.URLParams.Add("user", "octocat")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", nil)
|
||||
r = r.WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||
)
|
||||
|
||||
users := mock.NewMockUserStore(controller)
|
||||
users.EXPECT().FindLogin(gomock.Any(), mockUser.Login).Return(mockUser, nil)
|
||||
users.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil)
|
||||
|
||||
HandleTokenRotation(users)(w, r)
|
||||
if got, want := w.Code, 200; want != got {
|
||||
t.Errorf("Want response code %d, got %d", want, got)
|
||||
}
|
||||
|
||||
got, want := &userWithMessage{}, mockUser
|
||||
json.NewDecoder(w.Body).Decode(got)
|
||||
|
||||
ignore := cmpopts.IgnoreFields(core.User{}, "Hash")
|
||||
if diff := cmp.Diff(got.User, want, ignore); len(diff) != 0 {
|
||||
t.Errorf(diff)
|
||||
}
|
||||
if got.Message == "" {
|
||||
t.Errorf("Expect Message returned")
|
||||
}
|
||||
if got, want := mockUser.Hash, startingHash; got == want {
|
||||
t.Errorf("Expect user hash updated")
|
||||
}
|
||||
}
|
||||
|
||||
// the purpose of this unit test is to verify we fail safely when a non existing user is provided
|
||||
func TestToken_UserNotFound(t *testing.T) {
|
||||
controller := gomock.NewController(t)
|
||||
defer controller.Finish()
|
||||
|
||||
startingHash := "MjAxOC0wOC0xMVQxNTo1ODowN1o"
|
||||
mockUser := &core.User{
|
||||
ID: 1,
|
||||
Login: "octocat",
|
||||
Hash: startingHash,
|
||||
}
|
||||
c := new(chi.Context)
|
||||
c.URLParams.Add("user", "octocat")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/?rotate=true", nil)
|
||||
r = r.WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||
)
|
||||
|
||||
users := mock.NewMockUserStore(controller)
|
||||
users.EXPECT().FindLogin(gomock.Any(), mockUser.Login).Return(mockUser, nil)
|
||||
users.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errors.ErrNotFound)
|
||||
|
||||
HandleTokenRotation(users)(w, r)
|
||||
if got, want := w.Code, 500; want != got {
|
||||
t.Errorf("Want response code %d, got %d", want, got)
|
||||
}
|
||||
|
||||
got, want := new(errors.Error), errors.ErrNotFound
|
||||
json.NewDecoder(w.Body).Decode(got)
|
||||
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||
t.Errorf(diff)
|
||||
}
|
||||
}
|
||||
|
||||
// the purpose of this unit test is to verify we fail safely when a non existing user is provided
|
||||
func TestToken_UpdateError(t *testing.T) {
|
||||
controller := gomock.NewController(t)
|
||||
defer controller.Finish()
|
||||
|
||||
c := new(chi.Context)
|
||||
c.URLParams.Add("user", "octocat")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/?rotate=true", nil)
|
||||
r = r.WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, c),
|
||||
)
|
||||
|
||||
users := mock.NewMockUserStore(controller)
|
||||
users.EXPECT().FindLogin(gomock.Any(), mockUser.Login).Return(nil, errors.ErrNotFound)
|
||||
|
||||
HandleTokenRotation(users)(w, r)
|
||||
if got, want := w.Code, 404; want != got {
|
||||
t.Errorf("Want response code %d, got %d", want, got)
|
||||
}
|
||||
|
||||
got, want := new(errors.Error), errors.ErrNotFound
|
||||
json.NewDecoder(w.Body).Decode(got)
|
||||
if diff := cmp.Diff(got, want); len(diff) != 0 {
|
||||
t.Errorf(diff)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue