Skip to content

Commit

Permalink
Create org, group, provider and role per user
Browse files Browse the repository at this point in the history
Ref #691
  • Loading branch information
eleftherias committed Oct 2, 2023
1 parent cfaf822 commit 0a67ba7
Show file tree
Hide file tree
Showing 8 changed files with 1,339 additions and 1,298 deletions.
6 changes: 1 addition & 5 deletions cmd/cli/app/auth/auth_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,7 @@ will be saved to $XDG_CONFIG_HOME/mediator/credentials.json`,

if !registered {
fmt.Println("First login, registering user.")
// register the user and add them to organization 1
// TODO: register the user in their own organization
_, err = client.CreateUser(ctx, &pb.CreateUserRequest{
OrganizationId: 1,
})
_, err = client.CreateUser(ctx, &pb.CreateUserRequest{})
util.ExitNicelyOnError(err, "Error registering user")
}

Expand Down
7 changes: 0 additions & 7 deletions docs/docs/protodocs/proto.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 63 additions & 52 deletions internal/controlplane/handlers_organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,60 +68,12 @@ func (s *Server) CreateOrganization(ctx context.Context,

if in.CreateDefaultRecords {
// we need to create the default records for the organization
group, err := qtx.CreateGroup(ctx, db.CreateGroupParams{OrganizationID: org.ID,
Name: fmt.Sprintf("%s-admin", org.Name),
Description: sql.NullString{String: fmt.Sprintf("Default admin group for %s", org.Name), Valid: true},
IsProtected: true,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create default group: %v", err)
}

grp := pb.GroupRecord{GroupId: group.ID, OrganizationId: group.OrganizationID,
Name: group.Name, Description: group.Description.String,
IsProtected: group.IsProtected, CreatedAt: timestamppb.New(group.CreatedAt), UpdatedAt: timestamppb.New(group.UpdatedAt)}
response.DefaultGroup = &grp

// we can create the default role for org and for group
role, err := qtx.CreateRole(ctx, db.CreateRoleParams{
OrganizationID: org.ID,
Name: fmt.Sprintf("%s-org-admin", org.Name),
IsAdmin: true,
IsProtected: true,
})
defaultGroup, defaultRoles, err := CreateDefaultRecordsForOrg(ctx, qtx, org)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create default org role: %v", err)
}

roleGroup, err := qtx.CreateRole(ctx, db.CreateRoleParams{
OrganizationID: org.ID,
GroupID: sql.NullInt32{Int32: group.ID, Valid: true},
Name: fmt.Sprintf("%s-group-admin", org.Name),
IsAdmin: true,
IsProtected: true,
})

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create default group role: %v", err)
}

rl := pb.RoleRecord{Id: role.ID, OrganizationId: role.OrganizationID, Name: role.Name, IsAdmin: role.IsAdmin,
IsProtected: role.IsProtected, CreatedAt: timestamppb.New(role.CreatedAt), UpdatedAt: timestamppb.New(role.UpdatedAt)}
rg := pb.RoleRecord{Id: roleGroup.ID, Name: roleGroup.Name, GroupId: &roleGroup.GroupID.Int32,
IsAdmin: roleGroup.IsAdmin, IsProtected: roleGroup.IsProtected,
CreatedAt: timestamppb.New(roleGroup.CreatedAt), UpdatedAt: timestamppb.New(roleGroup.UpdatedAt)}
response.DefaultRoles = []*pb.RoleRecord{&rl, &rg}

// Create GitHub provider
_, err = qtx.CreateProvider(ctx, db.CreateProviderParams{
Name: github.Github,
GroupID: grp.GroupId,
Implements: github.Implements,
Definition: json.RawMessage(`{"github": {}}`),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create provider: %v", err)
return nil, err
}
response.DefaultGroup = defaultGroup
response.DefaultRoles = defaultRoles
}
// commit and return
err = s.store.Commit(tx)
Expand All @@ -132,6 +84,65 @@ func (s *Server) CreateOrganization(ctx context.Context,
return response, nil
}

// CreateDefaultRecordsForOrg creates the default records, such as groups, roles and provider for the organization
func CreateDefaultRecordsForOrg(ctx context.Context, qtx db.Querier,
org db.Organization) (*pb.GroupRecord, []*pb.RoleRecord, error) {
// we need to create the default records for the organization
group, err := qtx.CreateGroup(ctx, db.CreateGroupParams{OrganizationID: org.ID,
Name: fmt.Sprintf("%s-admin", org.Name),
Description: sql.NullString{String: fmt.Sprintf("Default admin group for %s", org.Name), Valid: true},
IsProtected: true,
})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "failed to create default group: %v", err)
}

grp := pb.GroupRecord{GroupId: group.ID, OrganizationId: group.OrganizationID,
Name: group.Name, Description: group.Description.String,
IsProtected: group.IsProtected, CreatedAt: timestamppb.New(group.CreatedAt), UpdatedAt: timestamppb.New(group.UpdatedAt)}

// we can create the default role for org and for group
role, err := qtx.CreateRole(ctx, db.CreateRoleParams{
OrganizationID: org.ID,
Name: fmt.Sprintf("%s-org-admin", org.Name),
IsAdmin: true,
IsProtected: true,
})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "failed to create default org role: %v", err)
}

roleGroup, err := qtx.CreateRole(ctx, db.CreateRoleParams{
OrganizationID: org.ID,
GroupID: sql.NullInt32{Int32: group.ID, Valid: true},
Name: fmt.Sprintf("%s-group-admin", org.Name),
IsAdmin: true,
IsProtected: true,
})

if err != nil {
return nil, nil, status.Errorf(codes.Internal, "failed to create default group role: %v", err)
}

rl := pb.RoleRecord{Id: role.ID, OrganizationId: role.OrganizationID, Name: role.Name, IsAdmin: role.IsAdmin,
IsProtected: role.IsProtected, CreatedAt: timestamppb.New(role.CreatedAt), UpdatedAt: timestamppb.New(role.UpdatedAt)}
rg := pb.RoleRecord{Id: roleGroup.ID, Name: roleGroup.Name, GroupId: &roleGroup.GroupID.Int32,
IsAdmin: roleGroup.IsAdmin, IsProtected: roleGroup.IsProtected,
CreatedAt: timestamppb.New(roleGroup.CreatedAt), UpdatedAt: timestamppb.New(roleGroup.UpdatedAt)}

// Create GitHub provider
_, err = qtx.CreateProvider(ctx, db.CreateProviderParams{
Name: github.Github,
GroupID: grp.GroupId,
Implements: github.Implements,
Definition: json.RawMessage(`{"github": {}}`),
})
if err != nil {
return nil, nil, status.Errorf(codes.Internal, "failed to create provider: %v", err)
}
return &grp, []*pb.RoleRecord{&rl, &rg}, nil
}

// GetOrganizations is a service for getting a list of organizations
func (s *Server) GetOrganizations(ctx context.Context,
in *pb.GetOrganizationsRequest) (*pb.GetOrganizationsResponse, error) {
Expand Down
106 changes: 41 additions & 65 deletions internal/controlplane/handlers_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ import (
pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1"
)

type createUserValidation struct {
OrganizationId int32 `db:"organization_id" validate:"required"`
}

func stringToNullString(s string) *sql.NullString {
if s == "" {
return &sql.NullString{Valid: false}
Expand All @@ -46,7 +42,7 @@ func stringToNullString(s string) *sql.NullString {
//
//gocyclo:ignore
func (s *Server) CreateUser(ctx context.Context,
in *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
_ *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {

tokenString, err := gauth.AuthFromMD(ctx, "bearer")
if err != nil {
Expand All @@ -58,56 +54,6 @@ func (s *Server) CreateUser(ctx context.Context,
return nil, status.Errorf(codes.Unauthenticated, "failed to parse bearer token: %v", err)
}

validator := validator.New()
format := createUserValidation{OrganizationId: in.OrganizationId}

err = validator.Struct(format)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid argument: %s", err.Error())
}

// if the token has superadmin access to the realm, then make give them a superadmin role in the DB
if containsAdminRole(token) {
in.RoleIds = append(in.RoleIds, 1)
in.GroupIds = append(in.GroupIds, 1)
}

// if we have group ids we check if they exist
if in.GroupIds != nil {
for _, id := range in.GroupIds {
group, err := s.store.GetGroupByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "group not found")
}
return nil, status.Errorf(codes.Internal, "failed to get group: %s", err)
}

// group must belong to org
if group.OrganizationID != in.OrganizationId {
return nil, status.Errorf(codes.InvalidArgument, "group does not belong to organization")
}
}
}

// if we have role ids we check if they exist
if in.RoleIds != nil {
for _, id := range in.RoleIds {
role, err := s.store.GetRoleByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "role not found")
}
return nil, status.Errorf(codes.Internal, "failed to get role: %s", err)
}

// role must belong to org
if role.OrganizationID != in.OrganizationId {
return nil, status.Errorf(codes.InvalidArgument, "role does not belong to organization")
}
}
}

tx, err := s.store.BeginTransaction()
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to begin transaction")
Expand All @@ -118,23 +64,53 @@ func (s *Server) CreateUser(ctx context.Context,
return nil, status.Errorf(codes.Internal, "failed to get transaction")
}

user, err := qtx.CreateUser(ctx, db.CreateUserParams{OrganizationID: in.OrganizationId,
subject := token.Subject()

var userOrg int32
var userGroup int32
var userRoles []int32

if containsSuperadminRole(token) {
// if the token has superadmin access to the realm, then make give them a superadmin role in the DB
userOrg = 1
userGroup = 0
userRoles = append(userRoles, 1)
} else {
// otherwise self-enroll user, by creating a new org and group and making the user an admin of those
organization, err := qtx.CreateOrganization(ctx, db.CreateOrganizationParams{
Name: subject + "-org",
Company: subject + " - Self enrolled",
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create organization: %s", err)
}
orgGroup, orgRoles, err := CreateDefaultRecordsForOrg(ctx, qtx, organization)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create default organization records: %s", err)
}

userOrg = organization.ID
userGroup = orgGroup.GroupId
for _, role := range orgRoles {
userRoles = append(userRoles, role.Id)
}
}

user, err := qtx.CreateUser(ctx, db.CreateUserParams{OrganizationID: userOrg,
Email: *stringToNullString(token.Email()),
FirstName: *stringToNullString(token.GivenName()),
LastName: *stringToNullString(token.FamilyName()),
IdentitySubject: token.Subject()})
IdentitySubject: subject})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create user: %s", err)
}

// now attach the groups and roles
for _, id := range in.GroupIds {
_, err := qtx.AddUserGroup(ctx, db.AddUserGroupParams{UserID: user.ID, GroupID: id})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to add user to group: %s", err)
}
_, err = qtx.AddUserGroup(ctx, db.AddUserGroupParams{UserID: user.ID, GroupID: userGroup})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to add user to group: %s", err)
}
for _, id := range in.RoleIds {

for _, id := range userRoles {
_, err := qtx.AddUserRole(ctx, db.AddUserRoleParams{UserID: user.ID, RoleID: id})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to add user to role: %s", err)
Expand All @@ -150,7 +126,7 @@ func (s *Server) CreateUser(ctx context.Context,
CreatedAt: timestamppb.New(user.CreatedAt), UpdatedAt: timestamppb.New(user.UpdatedAt)}, nil
}

func containsAdminRole(openIdToken openid.Token) bool {
func containsSuperadminRole(openIdToken openid.Token) bool {
if realmAccess, ok := openIdToken.Get("realm_access"); ok {
if realms, ok := realmAccess.(map[string]interface{}); ok {
if roles, ok := realms["roles"]; ok {
Expand Down
Loading

0 comments on commit 0a67ba7

Please sign in to comment.