forked from mystiq/dex
Merge pull request #140 from joeatwork/disable-users-api
Expose API to enable and disable users
This commit is contained in:
commit
a426943054
13 changed files with 442 additions and 12 deletions
19
db/user.go
19
db/user.go
|
@ -97,10 +97,29 @@ func (r *userRepo) Create(tx repo.Transaction, usr user.User) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.insert(tx, usr)
|
err = r.insert(tx, usr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userRepo) Disable(tx repo.Transaction, userID string, disable bool) error {
|
||||||
|
if userID == "" {
|
||||||
|
return user.ErrorInvalidID
|
||||||
|
}
|
||||||
|
|
||||||
|
qt := pq.QuoteIdentifier(userTableName)
|
||||||
|
ex := r.executor(tx)
|
||||||
|
result, err := ex.Exec(fmt.Sprintf("UPDATE %s SET disabled = $2 WHERE id = $1", qt), userID, disable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct, err := result.RowsAffected()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case ct == 0:
|
||||||
|
return user.ErrorNotFound
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ var (
|
||||||
ID: "ID-2",
|
ID: "ID-2",
|
||||||
Email: "Email-2@example.com",
|
Email: "Email-2@example.com",
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
Disabled: true,
|
||||||
},
|
},
|
||||||
RemoteIdentities: []user.RemoteIdentity{
|
RemoteIdentities: []user.RemoteIdentity{
|
||||||
{
|
{
|
||||||
|
@ -232,6 +233,61 @@ func TestUpdateUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableUser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
id string
|
||||||
|
disable bool
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
id: "ID-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-1",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-2",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "NO SUCH ID",
|
||||||
|
err: user.ErrorNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "NO SUCH ID",
|
||||||
|
err: user.ErrorNotFound,
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "",
|
||||||
|
err: user.ErrorInvalidID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
repo := makeTestUserRepo()
|
||||||
|
err := repo.Disable(nil, tt.id, tt.disable)
|
||||||
|
switch {
|
||||||
|
case err != tt.err:
|
||||||
|
t.Errorf("case %d: want=%q, got=%q", i, tt.err, err)
|
||||||
|
case tt.err == nil:
|
||||||
|
gotUser, err := repo.Get(nil, tt.id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: want nil err, got %q", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotUser.Disabled != tt.disable {
|
||||||
|
t.Errorf("case %d: disabled status want=%v got=%v",
|
||||||
|
i, tt.disable, gotUser.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAttachRemoteIdentity(t *testing.T) {
|
func TestAttachRemoteIdentity(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
id string
|
id string
|
||||||
|
|
|
@ -141,6 +141,7 @@ func makeUserAPITestFixtures() *userAPITestFixtures {
|
||||||
|
|
||||||
f.trans = &tokenHandlerTransport{
|
f.trans = &tokenHandlerTransport{
|
||||||
Handler: usrSrv.HTTPHandler(),
|
Handler: usrSrv.HTTPHandler(),
|
||||||
|
Token: userGoodToken,
|
||||||
}
|
}
|
||||||
hc := &http.Client{
|
hc := &http.Client{
|
||||||
Transport: f.trans,
|
Transport: f.trans,
|
||||||
|
@ -530,6 +531,48 @@ func TestCreateUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableUser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
id string
|
||||||
|
disable bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
id: "ID-2",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-4",
|
||||||
|
disable: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
f := makeUserAPITestFixtures()
|
||||||
|
|
||||||
|
usr, err := f.client.Users.Get(tt.id).Do()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
if usr.User.Disabled == tt.disable {
|
||||||
|
t.Fatalf("case %v: misconfigured test, initial disabled state should be %v but was %v", i, !tt.disable, usr.User.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.client.Users.Disable(tt.id, &schema.UserDisableRequest{
|
||||||
|
Disable: tt.disable,
|
||||||
|
}).Do()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
usr, err = f.client.Users.Get(tt.id).Do()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %v: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
if usr.User.Disabled != tt.disable {
|
||||||
|
t.Errorf("case %v: user disabled state incorrect. wanted: %v found: %v", i, tt.disable, usr.User.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testEmailer struct {
|
type testEmailer struct {
|
||||||
cantEmail bool
|
cantEmail bool
|
||||||
lastEmail string
|
lastEmail string
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//
|
//
|
||||||
// Usage example:
|
// Usage example:
|
||||||
//
|
//
|
||||||
// import "github.com/coreos/dex/Godeps/_workspace/src/google.golang.org/api/adminschema/v1"
|
// import "google.golang.org/api/adminschema/v1"
|
||||||
// ...
|
// ...
|
||||||
// adminschemaService, err := adminschema.New(oauthHttpClient)
|
// adminschemaService, err := adminschema.New(oauthHttpClient)
|
||||||
package adminschema
|
package adminschema
|
||||||
|
|
|
@ -108,6 +108,8 @@ type User struct {
|
||||||
|
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
|
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
|
||||||
DisplayName string `json:"displayName,omitempty"`
|
DisplayName string `json:"displayName,omitempty"`
|
||||||
|
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
|
@ -134,6 +136,15 @@ type UserCreateResponse struct {
|
||||||
type UserCreateResponseUser struct {
|
type UserCreateResponseUser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserDisableRequest struct {
|
||||||
|
// Disable: If true, disable this user, if false, enable them
|
||||||
|
Disable bool `json:"disable,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserDisableResponse struct {
|
||||||
|
Ok bool `json:"ok,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -355,6 +366,89 @@ func (c *UsersCreateCall) Do() (*UserCreateResponse, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// method id "dex.User.Disable":
|
||||||
|
|
||||||
|
type UsersDisableCall struct {
|
||||||
|
s *Service
|
||||||
|
id string
|
||||||
|
userdisablerequest *UserDisableRequest
|
||||||
|
opt_ map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable: Enable or disable a user.
|
||||||
|
func (r *UsersService) Disable(id string, userdisablerequest *UserDisableRequest) *UsersDisableCall {
|
||||||
|
c := &UsersDisableCall{s: r.s, opt_: make(map[string]interface{})}
|
||||||
|
c.id = id
|
||||||
|
c.userdisablerequest = userdisablerequest
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields allows partial responses to be retrieved.
|
||||||
|
// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||||
|
// for more information.
|
||||||
|
func (c *UsersDisableCall) Fields(s ...googleapi.Field) *UsersDisableCall {
|
||||||
|
c.opt_["fields"] = googleapi.CombineFields(s)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UsersDisableCall) Do() (*UserDisableResponse, error) {
|
||||||
|
var body io.Reader = nil
|
||||||
|
body, err := googleapi.WithoutDataWrapper.JSONReader(c.userdisablerequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctype := "application/json"
|
||||||
|
params := make(url.Values)
|
||||||
|
params.Set("alt", "json")
|
||||||
|
if v, ok := c.opt_["fields"]; ok {
|
||||||
|
params.Set("fields", fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
urls := googleapi.ResolveRelative(c.s.BasePath, "users/{id}/disable")
|
||||||
|
urls += "?" + params.Encode()
|
||||||
|
req, _ := http.NewRequest("POST", urls, body)
|
||||||
|
googleapi.Expand(req.URL, map[string]string{
|
||||||
|
"id": c.id,
|
||||||
|
})
|
||||||
|
req.Header.Set("Content-Type", ctype)
|
||||||
|
req.Header.Set("User-Agent", "google-api-go-client/0.5")
|
||||||
|
res, err := c.s.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer googleapi.CloseBody(res)
|
||||||
|
if err := googleapi.CheckResponse(res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var ret *UserDisableResponse
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
// {
|
||||||
|
// "description": "Enable or disable a user.",
|
||||||
|
// "httpMethod": "POST",
|
||||||
|
// "id": "dex.User.Disable",
|
||||||
|
// "parameterOrder": [
|
||||||
|
// "id"
|
||||||
|
// ],
|
||||||
|
// "parameters": {
|
||||||
|
// "id": {
|
||||||
|
// "location": "path",
|
||||||
|
// "required": true,
|
||||||
|
// "type": "string"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "path": "users/{id}/disable",
|
||||||
|
// "request": {
|
||||||
|
// "$ref": "UserDisableRequest"
|
||||||
|
// },
|
||||||
|
// "response": {
|
||||||
|
// "$ref": "UserDisableResponse"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// method id "dex.User.Get":
|
// method id "dex.User.Get":
|
||||||
|
|
||||||
type UsersGetCall struct {
|
type UsersGetCall struct {
|
||||||
|
@ -363,7 +457,7 @@ type UsersGetCall struct {
|
||||||
opt_ map[string]interface{}
|
opt_ map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get: Get a single use object.
|
// Get: Get a single User object by id.
|
||||||
func (r *UsersService) Get(id string) *UsersGetCall {
|
func (r *UsersService) Get(id string) *UsersGetCall {
|
||||||
c := &UsersGetCall{s: r.s, opt_: make(map[string]interface{})}
|
c := &UsersGetCall{s: r.s, opt_: make(map[string]interface{})}
|
||||||
c.id = id
|
c.id = id
|
||||||
|
@ -406,7 +500,7 @@ func (c *UsersGetCall) Do() (*UserResponse, error) {
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
// {
|
// {
|
||||||
// "description": "Get a single use object.",
|
// "description": "Get a single User object by id.",
|
||||||
// "httpMethod": "GET",
|
// "httpMethod": "GET",
|
||||||
// "id": "dex.User.Get",
|
// "id": "dex.User.Get",
|
||||||
// "parameterOrder": [
|
// "parameterOrder": [
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
package workerschema
|
package workerschema
|
||||||
|
|
||||||
//
|
//
|
||||||
// This file is automatically generated by schema/generator
|
// This file is automatically generated by schema/generator
|
||||||
//
|
//
|
||||||
|
@ -109,6 +108,9 @@ const DiscoveryJSON = `{
|
||||||
"admin": {
|
"admin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
@ -167,6 +169,25 @@ const DiscoveryJSON = `{
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"UserDisableRequest": {
|
||||||
|
"id": "UserDisableRequest",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"disable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, disable this user, if false, enable them"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserDisableResponse": {
|
||||||
|
"id": "UserDisableResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ok": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
|
@ -224,7 +245,7 @@ const DiscoveryJSON = `{
|
||||||
},
|
},
|
||||||
"Get": {
|
"Get": {
|
||||||
"id": "dex.User.Get",
|
"id": "dex.User.Get",
|
||||||
"description": "Get a single use object.",
|
"description": "Get a single User object by id.",
|
||||||
"httpMethod": "GET",
|
"httpMethod": "GET",
|
||||||
"path": "users/{id}",
|
"path": "users/{id}",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
@ -252,9 +273,31 @@ const DiscoveryJSON = `{
|
||||||
"response": {
|
"response": {
|
||||||
"$ref": "UserCreateResponse"
|
"$ref": "UserCreateResponse"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Disable": {
|
||||||
|
"id": "dex.User.Disable",
|
||||||
|
"description": "Enable or disable a user.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "users/{id}/disable",
|
||||||
|
"parameters": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"location": "path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameterOrder": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"$ref": "UserDisableRequest"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "UserDisableResponse"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
|
@ -102,6 +102,9 @@
|
||||||
"admin": {
|
"admin": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
@ -160,6 +163,25 @@
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"UserDisableRequest": {
|
||||||
|
"id": "UserDisableRequest",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"disable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, disable this user, if false, enable them. No error is signaled if the user state doesn't change."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserDisableResponse": {
|
||||||
|
"id": "UserDisableResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ok": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
|
@ -217,7 +239,7 @@
|
||||||
},
|
},
|
||||||
"Get": {
|
"Get": {
|
||||||
"id": "dex.User.Get",
|
"id": "dex.User.Get",
|
||||||
"description": "Get a single use object.",
|
"description": "Get a single User object by id.",
|
||||||
"httpMethod": "GET",
|
"httpMethod": "GET",
|
||||||
"path": "users/{id}",
|
"path": "users/{id}",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
@ -245,6 +267,28 @@
|
||||||
"response": {
|
"response": {
|
||||||
"$ref": "UserCreateResponse"
|
"$ref": "UserCreateResponse"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Disable": {
|
||||||
|
"id": "dex.User.Disable",
|
||||||
|
"description": "Enable or disable a user.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "users/{id}/disable",
|
||||||
|
"parameters": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"location": "path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameterOrder": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"$ref": "UserDisableRequest"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "UserDisableResponse"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,7 +244,11 @@ func (s *Server) HTTPHandler() http.Handler {
|
||||||
mux.Handle(path.Join(apiBasePath, clientPath), s.NewClientTokenAuthHandler(clientHandler))
|
mux.Handle(path.Join(apiBasePath, clientPath), s.NewClientTokenAuthHandler(clientHandler))
|
||||||
|
|
||||||
usersAPI := usersapi.NewUsersAPI(s.UserManager, s.ClientIdentityRepo, s.UserEmailer, s.localConnectorID)
|
usersAPI := usersapi.NewUsersAPI(s.UserManager, s.ClientIdentityRepo, s.UserEmailer, s.localConnectorID)
|
||||||
mux.Handle(path.Join(apiBasePath, UsersSubTree), NewUserMgmtServer(usersAPI, s.JWTVerifierFactory(), s.UserManager, s.ClientIdentityRepo).HTTPHandler())
|
handler := NewUserMgmtServer(usersAPI, s.JWTVerifierFactory(), s.UserManager, s.ClientIdentityRepo).HTTPHandler()
|
||||||
|
path := path.Join(apiBasePath, UsersSubTree)
|
||||||
|
|
||||||
|
mux.Handle(path, handler)
|
||||||
|
mux.Handle(path+"/", handler)
|
||||||
|
|
||||||
return http.Handler(mux)
|
return http.Handler(mux)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
UsersSubTree = "/users"
|
UsersSubTree = "/users"
|
||||||
UsersListEndpoint = addBasePath(UsersSubTree)
|
UsersListEndpoint = addBasePath(UsersSubTree)
|
||||||
UsersCreateEndpoint = addBasePath(UsersSubTree)
|
UsersCreateEndpoint = addBasePath(UsersSubTree)
|
||||||
UsersGetEndpoint = addBasePath(UsersSubTree + "/:id")
|
UsersGetEndpoint = addBasePath(UsersSubTree + "/:id")
|
||||||
|
UsersDisableEndpoint = addBasePath(UsersSubTree + "/:id/disable")
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserMgmtServer struct {
|
type UserMgmtServer struct {
|
||||||
|
@ -51,6 +52,7 @@ func (s *UserMgmtServer) HTTPHandler() http.Handler {
|
||||||
r.RedirectFixedPath = false
|
r.RedirectFixedPath = false
|
||||||
r.GET(UsersListEndpoint, s.listUsers)
|
r.GET(UsersListEndpoint, s.listUsers)
|
||||||
r.POST(UsersCreateEndpoint, s.createUser)
|
r.POST(UsersCreateEndpoint, s.createUser)
|
||||||
|
r.POST(UsersDisableEndpoint, s.disableUser)
|
||||||
r.GET(UsersGetEndpoint, s.getUser)
|
r.GET(UsersGetEndpoint, s.getUser)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -140,6 +142,35 @@ func (s *UserMgmtServer) createUser(w http.ResponseWriter, r *http.Request, ps h
|
||||||
writeResponseWithBody(w, http.StatusOK, createdResponse)
|
writeResponseWithBody(w, http.StatusOK, createdResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserMgmtServer) disableUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
creds, err := s.getCreds(r)
|
||||||
|
if err != nil {
|
||||||
|
s.writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ps.ByName("id")
|
||||||
|
if id == "" {
|
||||||
|
writeAPIError(w, http.StatusBadRequest,
|
||||||
|
newAPIError(errorInvalidRequest, "id is required"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
disableReq := schema.UserDisableRequest{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&disableReq)
|
||||||
|
if err != nil {
|
||||||
|
writeInvalidRequest(w, "cannot parse JSON body")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.api.DisableUser(creds, id, disableReq.Disable)
|
||||||
|
if err != nil {
|
||||||
|
s.writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponseWithBody(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserMgmtServer) writeError(w http.ResponseWriter, err error) {
|
func (s *UserMgmtServer) writeError(w http.ResponseWriter, err error) {
|
||||||
log.Errorf("Error calling user management API: %v: ", err)
|
log.Errorf("Error calling user management API: %v: ", err)
|
||||||
if apiErr, ok := err.(api.Error); ok {
|
if apiErr, ok := err.(api.Error); ok {
|
||||||
|
|
|
@ -121,6 +121,21 @@ func (u *UsersAPI) GetUser(creds Creds, id string) (schema.User, error) {
|
||||||
return userToSchemaUser(usr), nil
|
return userToSchemaUser(usr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UsersAPI) DisableUser(creds Creds, userID string, disable bool) (schema.UserDisableResponse, error) {
|
||||||
|
log.Infof("userAPI: DisableUser")
|
||||||
|
if !u.Authorize(creds) {
|
||||||
|
return schema.UserDisableResponse{}, ErrorUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.manager.Disable(userID, disable); err != nil {
|
||||||
|
return schema.UserDisableResponse{}, mapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema.UserDisableResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (schema.UserCreateResponse, error) {
|
func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (schema.UserCreateResponse, error) {
|
||||||
log.Infof("userAPI: CreateUser")
|
log.Infof("userAPI: CreateUser")
|
||||||
if !u.Authorize(creds) {
|
if !u.Authorize(creds) {
|
||||||
|
@ -207,6 +222,7 @@ func userToSchemaUser(usr user.User) schema.User {
|
||||||
EmailVerified: usr.EmailVerified,
|
EmailVerified: usr.EmailVerified,
|
||||||
DisplayName: usr.DisplayName,
|
DisplayName: usr.DisplayName,
|
||||||
Admin: usr.Admin,
|
Admin: usr.Admin,
|
||||||
|
Disabled: usr.Disabled,
|
||||||
CreatedAt: usr.CreatedAt.UTC().Format(time.RFC3339),
|
CreatedAt: usr.CreatedAt.UTC().Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,6 +234,7 @@ func schemaUserToUser(usr schema.User) user.User {
|
||||||
EmailVerified: usr.EmailVerified,
|
EmailVerified: usr.EmailVerified,
|
||||||
DisplayName: usr.DisplayName,
|
DisplayName: usr.DisplayName,
|
||||||
Admin: usr.Admin,
|
Admin: usr.Admin,
|
||||||
|
Disabled: usr.Disabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,13 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
|
||||||
Email: "id3@example.com",
|
Email: "id3@example.com",
|
||||||
CreatedAt: clock.Now(),
|
CreatedAt: clock.Now(),
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
User: user.User{
|
||||||
|
ID: "ID-4",
|
||||||
|
Email: "id4@example.com",
|
||||||
|
CreatedAt: clock.Now(),
|
||||||
|
Disabled: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
pwr := user.NewPasswordInfoRepoFromPasswordInfos([]user.PasswordInfo{
|
pwr := user.NewPasswordInfoRepoFromPasswordInfos([]user.PasswordInfo{
|
||||||
|
@ -369,3 +376,44 @@ func TestCreateUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableUsers(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
id string
|
||||||
|
disable bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
id: "ID-1",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-1",
|
||||||
|
disable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-4",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ID-4",
|
||||||
|
disable: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
api, _ := makeTestFixtures()
|
||||||
|
_, err := api.DisableUser(goodCreds, tt.id, tt.disable)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
usr, err := api.GetUser(goodCreds, tt.id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("case %d: unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usr.Disabled != tt.disable {
|
||||||
|
t.Errorf("case %d: user disable state wrong. wanted: %v got: %v", i, tt.disable, usr.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,22 @@ func (m *Manager) CreateUser(user User, hashedPassword Password, connID string)
|
||||||
return user.ID, nil
|
return user.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Disable(userID string, disabled bool) error {
|
||||||
|
tx, err := m.begin()
|
||||||
|
|
||||||
|
if err = m.userRepo.Disable(tx, userID, disabled); err != nil {
|
||||||
|
rollback(tx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
rollback(tx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterWithRemoteIdentity creates new user and attaches the given remote identity.
|
// RegisterWithRemoteIdentity creates new user and attaches the given remote identity.
|
||||||
func (m *Manager) RegisterWithRemoteIdentity(email string, emailVerified bool, rid RemoteIdentity) (string, error) {
|
func (m *Manager) RegisterWithRemoteIdentity(email string, emailVerified bool, rid RemoteIdentity) (string, error) {
|
||||||
tx, err := m.begin()
|
tx, err := m.begin()
|
||||||
|
|
15
user/user.go
15
user/user.go
|
@ -80,6 +80,8 @@ type UserRepo interface {
|
||||||
|
|
||||||
GetByEmail(tx repo.Transaction, email string) (User, error)
|
GetByEmail(tx repo.Transaction, email string) (User, error)
|
||||||
|
|
||||||
|
Disable(tx repo.Transaction, id string, disabled bool) error
|
||||||
|
|
||||||
Update(repo.Transaction, User) error
|
Update(repo.Transaction, User) error
|
||||||
|
|
||||||
GetByRemoteIdentity(repo.Transaction, RemoteIdentity) (User, error)
|
GetByRemoteIdentity(repo.Transaction, RemoteIdentity) (User, error)
|
||||||
|
@ -254,6 +256,19 @@ func (r *memUserRepo) Update(_ repo.Transaction, user User) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *memUserRepo) Disable(_ repo.Transaction, id string, disable bool) error {
|
||||||
|
if id == "" {
|
||||||
|
return ErrorInvalidID
|
||||||
|
}
|
||||||
|
user, ok := r.usersByID[id]
|
||||||
|
if !ok {
|
||||||
|
return ErrorNotFound
|
||||||
|
}
|
||||||
|
user.Disabled = disable
|
||||||
|
r.set(user)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *memUserRepo) AddRemoteIdentity(_ repo.Transaction, userID string, ri RemoteIdentity) error {
|
func (r *memUserRepo) AddRemoteIdentity(_ repo.Transaction, userID string, ri RemoteIdentity) error {
|
||||||
_, ok := r.usersByID[userID]
|
_, ok := r.usersByID[userID]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Reference in a new issue