Skip to content

Commit

Permalink
Read groups from oauth provider and allow roles to groups
Browse files Browse the repository at this point in the history
  • Loading branch information
cezarsa committed Jul 24, 2020
1 parent d8c6235 commit 292f083
Show file tree
Hide file tree
Showing 24 changed files with 562 additions and 19 deletions.
67 changes: 67 additions & 0 deletions api/permission.go
Expand Up @@ -762,3 +762,70 @@ func dissociateRoleFromToken(w http.ResponseWriter, r *http.Request, t auth.Toke
}
return err
}

// title: assign role to group
// path: /roles/{name}/group
// method: POST
// consume: application/x-www-form-urlencoded
// responses:
// 200: Ok
// 400: Invalid data
// 401: Unauthorized
// 404: Role not found
func assignRoleToGroup(w http.ResponseWriter, r *http.Request, t auth.Token) error {
if !permission.Check(t, permission.PermRoleUpdateAssign) {
return permission.ErrUnauthorized
}
groupName := InputValue(r, "group_name")
contextValue := InputValue(r, "context")
roleName := r.URL.Query().Get(":name")
evt, err := event.New(&event.Opts{
Target: event.Target{Type: event.TargetTypeRole, Value: roleName},
Kind: permission.PermRoleUpdateAssign,
Owner: t,
CustomData: event.FormToCustomData(InputFields(r)),
Allowed: event.Allowed(permission.PermRoleReadEvents),
})
if err != nil {
return err
}
defer func() { evt.Done(err) }()
err = canUseRole(t, roleName, contextValue)
if err != nil {
return err
}
return servicemanager.AuthGroup.AddRole(groupName, roleName, contextValue)
}

// title: dissociate role from group
// path: /roles/{name}/group/{group_name}
// method: DELETE
// responses:
// 200: Ok
// 400: Invalid data
// 401: Unauthorized
// 404: Role not found
func dissociateRoleFromGroup(w http.ResponseWriter, r *http.Request, t auth.Token) error {
if !permission.Check(t, permission.PermRoleUpdateDissociate) {
return permission.ErrUnauthorized
}
groupName := r.URL.Query().Get(":group_name")
contextValue := InputValue(r, "context")
roleName := r.URL.Query().Get(":name")
evt, err := event.New(&event.Opts{
Target: event.Target{Type: event.TargetTypeRole, Value: roleName},
Kind: permission.PermRoleUpdateDissociate,
Owner: t,
CustomData: event.FormToCustomData(InputFields(r)),
Allowed: event.Allowed(permission.PermRoleReadEvents),
})
if err != nil {
return err
}
defer func() { evt.Done(err) }()
err = canUseRole(t, roleName, contextValue)
if err != nil {
return err
}
return servicemanager.AuthGroup.RemoveRole(groupName, roleName, contextValue)
}
6 changes: 6 additions & 0 deletions api/server.go
Expand Up @@ -162,6 +162,10 @@ func setupServices() error {
return err
}
servicemanager.AppVersion, err = version.AppVersionService()
if err != nil {
return err
}
servicemanager.AuthGroup, err = auth.GroupService()
return err
}

Expand Down Expand Up @@ -383,6 +387,8 @@ func RunServer(dry bool) http.Handler {
m.Add("1.0", "Get", "/permissions", AuthorizationRequiredHandler(listPermissions))
m.Add("1.6", "Post", "/roles/{name}/token", AuthorizationRequiredHandler(assignRoleToToken))
m.Add("1.6", "Delete", "/roles/{name}/token/{token_id}", AuthorizationRequiredHandler(dissociateRoleFromToken))
m.Add("1.9", "Post", "/roles/{name}/group", AuthorizationRequiredHandler(assignRoleToGroup))
m.Add("1.9", "Delete", "/roles/{name}/group/{group_name}", AuthorizationRequiredHandler(dissociateRoleFromGroup))

m.Add("1.0", "Get", "/debug/goroutines", AuthorizationRequiredHandler(dumpGoroutines))
m.Add("1.0", "Get", "/debug/pprof/", AuthorizationRequiredHandler(indexHandler))
Expand Down
57 changes: 57 additions & 0 deletions auth/group.go
@@ -0,0 +1,57 @@
// Copyright 2020 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package auth

import (
"github.com/pkg/errors"
"github.com/tsuru/tsuru/permission"
"github.com/tsuru/tsuru/storage"
authTypes "github.com/tsuru/tsuru/types/auth"
)

var (
_ authTypes.GroupService = &groupService{}

errGroupNameEmpty = errors.New("group name cannot be empty")
)

func GroupService() (authTypes.GroupService, error) {
dbDriver, err := storage.GetCurrentDbDriver()
if err != nil {
dbDriver, err = storage.GetDefaultDbDriver()
if err != nil {
return nil, err
}
}
return &groupService{
storage: dbDriver.AuthGroupStorage,
}, nil
}

type groupService struct {
storage authTypes.GroupStorage
}

func (s *groupService) List(filter []string) ([]authTypes.Group, error) {
return s.storage.List(filter)
}

func (s *groupService) AddRole(name, roleName, contextValue string) error {
if name == "" {
return errGroupNameEmpty
}
_, err := permission.FindRole(roleName)
if err != nil {
return err
}
return s.storage.AddRole(name, roleName, contextValue)
}

func (s *groupService) RemoveRole(name, roleName, contextValue string) error {
if name == "" {
return errGroupNameEmpty
}
return s.storage.RemoveRole(name, roleName, contextValue)
}
43 changes: 27 additions & 16 deletions auth/oauth/oauth.go
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/tsuru/tsuru/auth/native"
tsuruErrors "github.com/tsuru/tsuru/errors"
"github.com/tsuru/tsuru/log"
"github.com/tsuru/tsuru/set"
authTypes "github.com/tsuru/tsuru/types/auth"
"golang.org/x/oauth2"
)
Expand Down Expand Up @@ -135,14 +136,14 @@ func (s *oAuthScheme) handleToken(t *oauth2.Token) (*tokenWrapper, error) {
return nil, err
}
defer response.Body.Close()
email, err := s.parse(response)
user, err := s.parse(response)
if err != nil {
return nil, err
}
if email == "" {
if user.Email == "" {
return nil, ErrEmptyUserEmail
}
_, err = auth.GetUserByEmail(email)
dbUser, err := auth.GetUserByEmail(user.Email)
if err != nil {
if err != authTypes.ErrUserNotFound {
return nil, err
Expand All @@ -151,13 +152,20 @@ func (s *oAuthScheme) handleToken(t *oauth2.Token) (*tokenWrapper, error) {
if !registrationEnabled {
return nil, err
}
user := &auth.User{Email: email}
err = user.Create()
if err != nil {
return nil, err
dbUser = &auth.User{Email: user.Email}
err = dbUser.Create()
} else {
dbGroups := set.FromSlice(dbUser.Groups)
providerGroups := set.FromSlice(user.Groups)
if !dbGroups.Equal(providerGroups) {
dbUser.Groups = user.Groups
err = dbUser.Update()
}
}
token := tokenWrapper{Token: *t, UserEmail: email}
if err != nil {
return nil, err
}
token := tokenWrapper{Token: *t, UserEmail: user.Email}
err = token.save()
if err != nil {
return nil, err
Expand Down Expand Up @@ -208,22 +216,25 @@ func (s *oAuthScheme) Info() (auth.SchemeInfo, error) {
return auth.SchemeInfo{"authorizeUrl": config.AuthCodeURL(""), "port": strconv.Itoa(s.callbackPort)}, nil
}

func (s *oAuthScheme) parse(infoResponse *http.Response) (string, error) {
user := struct {
Email string `json:"email"`
}{}
type userData struct {
Email string `json:"email"`
Groups []string `json:"groups"`
}

func (s *oAuthScheme) parse(infoResponse *http.Response) (userData, error) {
var user userData
data, err := ioutil.ReadAll(infoResponse.Body)
if err != nil {
return "", errors.Wrap(err, "unable to read user data response")
return user, errors.Wrap(err, "unable to read user data response")
}
if infoResponse.StatusCode != http.StatusOK {
return "", errors.Errorf("unexpected user data response %d: %s", infoResponse.StatusCode, data)
return user, errors.Errorf("unexpected user data response %d: %s", infoResponse.StatusCode, data)
}
err = json.Unmarshal(data, &user)
if err != nil {
return "", errors.Wrapf(err, "unable to parse user data: %s", data)
return user, errors.Wrapf(err, "unable to parse user data: %s", data)
}
return user.Email, nil
return user, nil
}

func (s *oAuthScheme) Create(user *auth.User) (*auth.User, error) {
Expand Down
11 changes: 10 additions & 1 deletion auth/oauth/oauth_test.go
Expand Up @@ -132,7 +132,16 @@ func (s *S) TestOAuthParse(c *check.C) {
parser := &oAuthScheme{}
email, err := parser.parse(rsp)
c.Assert(err, check.IsNil)
c.Assert(email, check.Equals, "x@x.com")
c.Assert(email, check.DeepEquals, userData{Email: "x@x.com"})
}

func (s *S) TestOAuthParseWithGroups(c *check.C) {
b := ioutil.NopCloser(bytes.NewBufferString(`{"email":"x@x.com", "groups": ["g1", "g2"]}`))
rsp := &http.Response{Body: b, StatusCode: http.StatusOK}
parser := &oAuthScheme{}
email, err := parser.parse(rsp)
c.Assert(err, check.IsNil)
c.Assert(email, check.DeepEquals, userData{Email: "x@x.com", Groups: []string{"g1", "g2"}})
}

func (s *S) TestOAuthParseInvalid(c *check.C) {
Expand Down
2 changes: 2 additions & 0 deletions auth/suite_test.go
Expand Up @@ -51,6 +51,8 @@ func (s *S) SetUpSuite(c *check.C) {
c.Assert(err, check.IsNil)
servicemanager.Team, err = TeamService()
c.Assert(err, check.IsNil)
servicemanager.AuthGroup, err = GroupService()
c.Assert(err, check.IsNil)
}

func (s *S) TearDownSuite(c *check.C) {
Expand Down
16 changes: 15 additions & 1 deletion auth/user.go
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/tsuru/tsuru/log"
"github.com/tsuru/tsuru/permission"
"github.com/tsuru/tsuru/repository"
"github.com/tsuru/tsuru/servicemanager"
authTypes "github.com/tsuru/tsuru/types/auth"
permTypes "github.com/tsuru/tsuru/types/permission"
"github.com/tsuru/tsuru/types/quota"
Expand All @@ -31,6 +32,7 @@ type User struct {
Password string
APIKey string
Roles []authTypes.RoleInstance `bson:",omitempty"`
Groups []string `bson:",omitempty"`
}

func listUsers(filter bson.M) ([]User, error) {
Expand Down Expand Up @@ -247,7 +249,19 @@ func expandRolePermissions(roleInstances []authTypes.RoleInstance) ([]permission
}

func (u *User) Permissions() ([]permission.Permission, error) {
permissions, err := expandRolePermissions(u.Roles)
groupsFilter := []string{}
if u.Groups != nil {
groupsFilter = u.Groups
}
groups, err := servicemanager.AuthGroup.List(groupsFilter)
if err != nil {
return nil, err
}
allRoles := u.Roles
for _, group := range groups {
allRoles = append(allRoles, group.Roles...)
}
permissions, err := expandRolePermissions(allRoles)
if err != nil {
return nil, err
}
Expand Down
31 changes: 31 additions & 0 deletions auth/user_test.go
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/tsuru/tsuru/permission"
"github.com/tsuru/tsuru/repository"
"github.com/tsuru/tsuru/repository/repositorytest"
"github.com/tsuru/tsuru/servicemanager"
authTypes "github.com/tsuru/tsuru/types/auth"
permTypes "github.com/tsuru/tsuru/types/permission"
check "gopkg.in/check.v1"
Expand Down Expand Up @@ -402,6 +403,36 @@ func (s *S) TestUserPermissions(c *check.C) {
})
}

func (s *S) TestUserPermissionsIncludeGroups(c *check.C) {
u := User{Email: "me@tsuru.com", Password: "123", Groups: []string{"g1", "g2"}}
err := u.Create()
c.Assert(err, check.IsNil)
r1, err := permission.NewRole("r1", "app", "")
c.Assert(err, check.IsNil)
err = r1.AddPermissions("app.update.env", "app.deploy")
c.Assert(err, check.IsNil)
err = u.AddRole("r1", "myapp")
c.Assert(err, check.IsNil)
err = u.AddRole("r1", "myapp2")
c.Assert(err, check.IsNil)
err = servicemanager.AuthGroup.AddRole("g2", "r1", "myapp3")
c.Assert(err, check.IsNil)
err = servicemanager.AuthGroup.AddRole("g3", "r1", "myapp4")
c.Assert(err, check.IsNil)

perms, err := u.Permissions()
c.Assert(err, check.IsNil)
c.Assert(perms, check.DeepEquals, []permission.Permission{
{Scheme: permission.PermUser, Context: permission.Context(permTypes.CtxUser, u.Email)},
{Scheme: permission.PermAppDeploy, Context: permission.Context(permTypes.CtxApp, "myapp")},
{Scheme: permission.PermAppUpdateEnv, Context: permission.Context(permTypes.CtxApp, "myapp")},
{Scheme: permission.PermAppDeploy, Context: permission.Context(permTypes.CtxApp, "myapp2")},
{Scheme: permission.PermAppUpdateEnv, Context: permission.Context(permTypes.CtxApp, "myapp2")},
{Scheme: permission.PermAppDeploy, Context: permission.Context(permTypes.CtxApp, "myapp3")},
{Scheme: permission.PermAppUpdateEnv, Context: permission.Context(permTypes.CtxApp, "myapp3")},
})
}

func (s *S) TestUserPermissionsWithRemovedRole(c *check.C) {
role, err := permission.NewRole("test", "team", "")
c.Assert(err, check.IsNil)
Expand Down
1 change: 1 addition & 0 deletions cmd/tsurud/token.go
Expand Up @@ -15,6 +15,7 @@ import (
_ "github.com/tsuru/tsuru/auth/oauth"
"github.com/tsuru/tsuru/cmd"
"github.com/tsuru/tsuru/permission"
_ "github.com/tsuru/tsuru/storage/mongodb"
permTypes "github.com/tsuru/tsuru/types/permission"
)

Expand Down
3 changes: 2 additions & 1 deletion cmd/tsurud/token_test.go
Expand Up @@ -60,7 +60,8 @@ func (s *S) TestCreateRootUserCmdRun(c *check.C) {
}
manager := cmd.NewManager("glb", "", "", &stdout, &stderr, os.Stdin, nil)
client := cmd.NewClient(&http.Client{}, nil, manager)
command := createRootUserCmd{}
command := &tsurudCommand{Command: createRootUserCmd{}}
command.Flags().Parse(true, []string{"--config", "testdata/tsuru.conf"})
err := command.Run(&context, client)
c.Assert(err, check.IsNil)
c.Assert(stdout.String(), check.Equals, "Password: \nConfirm: \nRoot user successfully created.\n")
Expand Down

0 comments on commit 292f083

Please sign in to comment.