diff --git a/api/authorizations.go b/api/authorizations.go new file mode 100644 index 000000000..a1e782123 --- /dev/null +++ b/api/authorizations.go @@ -0,0 +1,266 @@ +package portainer + +// AuthorizationService represents a service used to +// update authorizations associated to a user or team. +type AuthorizationService struct { + endpointService EndpointService + endpointGroupService EndpointGroupService + roleService RoleService + teamMembershipService TeamMembershipService + userService UserService +} + +// AuthorizationServiceParameters are the required parameters +// used to create a new AuthorizationService. +type AuthorizationServiceParameters struct { + EndpointService EndpointService + EndpointGroupService EndpointGroupService + RoleService RoleService + TeamMembershipService TeamMembershipService + UserService UserService +} + +// NewAuthorizationService returns a point to a new AuthorizationService instance. +func NewAuthorizationService(parameters *AuthorizationServiceParameters) *AuthorizationService { + return &AuthorizationService{ + endpointService: parameters.EndpointService, + endpointGroupService: parameters.EndpointGroupService, + roleService: parameters.RoleService, + teamMembershipService: parameters.TeamMembershipService, + userService: parameters.UserService, + } +} + +// DefaultPortainerAuthorizations returns the default Portainer authorizations used by non-admin users. +func DefaultPortainerAuthorizations() Authorizations { + return map[Authorization]bool{ + OperationPortainerDockerHubInspect: true, + OperationPortainerEndpointGroupList: true, + OperationPortainerEndpointList: true, + OperationPortainerEndpointInspect: true, + OperationPortainerEndpointExtensionAdd: true, + OperationPortainerEndpointExtensionRemove: true, + OperationPortainerExtensionList: true, + OperationPortainerMOTD: true, + OperationPortainerRegistryList: true, + OperationPortainerRegistryInspect: true, + OperationPortainerTeamList: true, + OperationPortainerTemplateList: true, + OperationPortainerTemplateInspect: true, + OperationPortainerUserList: true, + OperationPortainerUserInspect: true, + OperationPortainerUserMemberships: true, + } +} + +// UpdateUserAuthorizationsFromPolicies will update users authorizations based on the specified access policies. +func (service *AuthorizationService) UpdateUserAuthorizationsFromPolicies(userPolicies *UserAccessPolicies, teamPolicies *TeamAccessPolicies) error { + + for userID, policy := range *userPolicies { + if policy.RoleID == 0 { + continue + } + + err := service.UpdateUserAuthorizations(userID) + if err != nil { + return err + } + } + + for teamID, policy := range *teamPolicies { + if policy.RoleID == 0 { + continue + } + + err := service.updateUserAuthorizationsInTeam(teamID) + if err != nil { + return err + } + } + + return nil +} + +func (service *AuthorizationService) updateUserAuthorizationsInTeam(teamID TeamID) error { + + memberships, err := service.teamMembershipService.TeamMembershipsByTeamID(teamID) + if err != nil { + return err + } + + for _, membership := range memberships { + err := service.UpdateUserAuthorizations(membership.UserID) + if err != nil { + return err + } + } + + return nil +} + +// UpdateUserAuthorizations will trigger an update of the authorizations for the specified user. +func (service *AuthorizationService) UpdateUserAuthorizations(userID UserID) error { + user, err := service.userService.User(userID) + if err != nil { + return err + } + + endpointAuthorizations, err := service.getAuthorizations(user) + if err != nil { + return err + } + + user.EndpointAuthorizations = endpointAuthorizations + + return service.userService.UpdateUser(userID, user) +} + +func (service *AuthorizationService) getAuthorizations(user *User) (EndpointAuthorizations, error) { + endpointAuthorizations := EndpointAuthorizations{} + if user.Role == AdministratorRole { + return endpointAuthorizations, nil + } + + userMemberships, err := service.teamMembershipService.TeamMembershipsByUserID(user.ID) + if err != nil { + return endpointAuthorizations, err + } + + endpoints, err := service.endpointService.Endpoints() + if err != nil { + return endpointAuthorizations, err + } + + endpointGroups, err := service.endpointGroupService.EndpointGroups() + if err != nil { + return endpointAuthorizations, err + } + + roles, err := service.roleService.Roles() + if err != nil { + return endpointAuthorizations, err + } + + endpointAuthorizations = getUserEndpointAuthorizations(user, endpoints, endpointGroups, roles, userMemberships) + + return endpointAuthorizations, nil +} + +func getUserEndpointAuthorizations(user *User, endpoints []Endpoint, endpointGroups []EndpointGroup, roles []Role, userMemberships []TeamMembership) EndpointAuthorizations { + endpointAuthorizations := make(EndpointAuthorizations) + + groupUserAccessPolicies := map[EndpointGroupID]UserAccessPolicies{} + groupTeamAccessPolicies := map[EndpointGroupID]TeamAccessPolicies{} + for _, endpointGroup := range endpointGroups { + groupUserAccessPolicies[endpointGroup.ID] = endpointGroup.UserAccessPolicies + groupTeamAccessPolicies[endpointGroup.ID] = endpointGroup.TeamAccessPolicies + } + + for _, endpoint := range endpoints { + authorizations := getAuthorizationsFromUserEndpointPolicy(user, &endpoint, roles) + if len(authorizations) > 0 { + endpointAuthorizations[endpoint.ID] = authorizations + continue + } + + authorizations = getAuthorizationsFromUserEndpointGroupPolicy(user, &endpoint, roles, groupUserAccessPolicies) + if len(authorizations) > 0 { + endpointAuthorizations[endpoint.ID] = authorizations + continue + } + + authorizations = getAuthorizationsFromTeamEndpointPolicies(userMemberships, &endpoint, roles) + if len(authorizations) > 0 { + endpointAuthorizations[endpoint.ID] = authorizations + continue + } + + endpointAuthorizations[endpoint.ID] = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies) + } + + return endpointAuthorizations +} + +func getAuthorizationsFromUserEndpointPolicy(user *User, endpoint *Endpoint, roles []Role) Authorizations { + policyRoles := make([]RoleID, 0) + + policy, ok := endpoint.UserAccessPolicies[user.ID] + if ok { + policyRoles = append(policyRoles, policy.RoleID) + } + + return getAuthorizationsFromRoles(policyRoles, roles) +} + +func getAuthorizationsFromUserEndpointGroupPolicy(user *User, endpoint *Endpoint, roles []Role, groupAccessPolicies map[EndpointGroupID]UserAccessPolicies) Authorizations { + policyRoles := make([]RoleID, 0) + + policy, ok := groupAccessPolicies[endpoint.GroupID][user.ID] + if ok { + policyRoles = append(policyRoles, policy.RoleID) + } + + return getAuthorizationsFromRoles(policyRoles, roles) +} + +func getAuthorizationsFromTeamEndpointPolicies(memberships []TeamMembership, endpoint *Endpoint, roles []Role) Authorizations { + policyRoles := make([]RoleID, 0) + + for _, membership := range memberships { + policy, ok := endpoint.TeamAccessPolicies[membership.TeamID] + if ok { + policyRoles = append(policyRoles, policy.RoleID) + } + } + + return getAuthorizationsFromRoles(policyRoles, roles) +} + +func getAuthorizationsFromTeamEndpointGroupPolicies(memberships []TeamMembership, endpoint *Endpoint, roles []Role, groupAccessPolicies map[EndpointGroupID]TeamAccessPolicies) Authorizations { + policyRoles := make([]RoleID, 0) + + for _, membership := range memberships { + policy, ok := groupAccessPolicies[endpoint.GroupID][membership.TeamID] + if ok { + policyRoles = append(policyRoles, policy.RoleID) + } + } + + return getAuthorizationsFromRoles(policyRoles, roles) +} + +func getAuthorizationsFromRoles(roleIdentifiers []RoleID, roles []Role) Authorizations { + var roleAuthorizations []Authorizations + for _, id := range roleIdentifiers { + for _, role := range roles { + if role.ID == id { + roleAuthorizations = append(roleAuthorizations, role.Authorizations) + break + } + } + } + + processedAuthorizations := make(Authorizations) + if len(roleAuthorizations) > 0 { + processedAuthorizations = roleAuthorizations[0] + for idx, authorizations := range roleAuthorizations { + if idx == 0 { + continue + } + processedAuthorizations = mergeAuthorizations(processedAuthorizations, authorizations) + } + } + + return processedAuthorizations +} + +func mergeAuthorizations(a, b Authorizations) Authorizations { + c := make(map[Authorization]bool) + + for k := range b { + if _, ok := a[k]; ok { + c[k] = true + } + } + return c +} diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go index 42100f514..cbb7a7b24 100644 --- a/api/bolt/datastore.go +++ b/api/bolt/datastore.go @@ -124,8 +124,10 @@ func (store *Store) MigrateData() error { ExtensionService: store.ExtensionService, RegistryService: store.RegistryService, ResourceControlService: store.ResourceControlService, + RoleService: store.RoleService, SettingsService: store.SettingsService, StackService: store.StackService, + TeamMembershipService: store.TeamMembershipService, TemplateService: store.TemplateService, UserService: store.UserService, VersionService: store.VersionService, diff --git a/api/bolt/migrator/migrate_dbversion19.go b/api/bolt/migrator/migrate_dbversion19.go new file mode 100644 index 000000000..c6020e658 --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion19.go @@ -0,0 +1,29 @@ +package migrator + +import portainer "github.com/portainer/portainer/api" + +func (m *Migrator) updateUsersToDBVersion20() error { + legacyUsers, err := m.userService.Users() + if err != nil { + return err + } + + authorizationServiceParameters := &portainer.AuthorizationServiceParameters{ + EndpointService: m.endpointService, + EndpointGroupService: m.endpointGroupService, + RoleService: m.roleService, + TeamMembershipService: m.teamMembershipService, + UserService: m.userService, + } + + authorizationService := portainer.NewAuthorizationService(authorizationServiceParameters) + + for _, user := range legacyUsers { + err := authorizationService.UpdateUserAuthorizations(user.ID) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 63a51f150..409343cf1 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -8,8 +8,10 @@ import ( "github.com/portainer/portainer/api/bolt/extension" "github.com/portainer/portainer/api/bolt/registry" "github.com/portainer/portainer/api/bolt/resourcecontrol" + "github.com/portainer/portainer/api/bolt/role" "github.com/portainer/portainer/api/bolt/settings" "github.com/portainer/portainer/api/bolt/stack" + "github.com/portainer/portainer/api/bolt/teammembership" "github.com/portainer/portainer/api/bolt/template" "github.com/portainer/portainer/api/bolt/user" "github.com/portainer/portainer/api/bolt/version" @@ -25,8 +27,10 @@ type ( extensionService *extension.Service registryService *registry.Service resourceControlService *resourcecontrol.Service + roleService *role.Service settingsService *settings.Service stackService *stack.Service + teamMembershipService *teammembership.Service templateService *template.Service userService *user.Service versionService *version.Service @@ -42,8 +46,10 @@ type ( ExtensionService *extension.Service RegistryService *registry.Service ResourceControlService *resourcecontrol.Service + RoleService *role.Service SettingsService *settings.Service StackService *stack.Service + TeamMembershipService *teammembership.Service TemplateService *template.Service UserService *user.Service VersionService *version.Service @@ -61,7 +67,9 @@ func NewMigrator(parameters *Parameters) *Migrator { extensionService: parameters.ExtensionService, registryService: parameters.RegistryService, resourceControlService: parameters.ResourceControlService, + roleService: parameters.RoleService, settingsService: parameters.SettingsService, + teamMembershipService: parameters.TeamMembershipService, templateService: parameters.TemplateService, stackService: parameters.StackService, userService: parameters.UserService, @@ -257,5 +265,13 @@ func (m *Migrator) Migrate() error { } } + // Portainer 1.22.x + if m.currentDBVersion < 20 { + err := m.updateUsersToDBVersion20() + if err != nil { + return err + } + } + return m.versionService.StoreDBVersion(portainer.DBVersion) } diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 7f4d36635..726be6da2 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -635,26 +635,10 @@ func main() { if len(users) == 0 { log.Printf("Creating admin user with password hash %s", adminPasswordHash) user := &portainer.User{ - Username: "admin", - Role: portainer.AdministratorRole, - Password: adminPasswordHash, - PortainerAuthorizations: map[portainer.Authorization]bool{ - portainer.OperationPortainerDockerHubInspect: true, - portainer.OperationPortainerEndpointGroupList: true, - portainer.OperationPortainerEndpointList: true, - portainer.OperationPortainerEndpointInspect: true, - portainer.OperationPortainerEndpointExtensionAdd: true, - portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, - portainer.OperationPortainerMOTD: true, - portainer.OperationPortainerRegistryList: true, - portainer.OperationPortainerRegistryInspect: true, - portainer.OperationPortainerTeamList: true, - portainer.OperationPortainerTemplateList: true, - portainer.OperationPortainerTemplateInspect: true, - portainer.OperationPortainerUserList: true, - portainer.OperationPortainerUserMemberships: true, - }, + Username: "admin", + Role: portainer.AdministratorRole, + Password: adminPasswordHash, + PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(), } err := store.UserService.CreateUser(user) if err != nil { diff --git a/api/http/handler/auth/authenticate.go b/api/http/handler/auth/authenticate.go index 9acfd78e3..a2f29e6f9 100644 --- a/api/http/handler/auth/authenticate.go +++ b/api/http/handler/auth/authenticate.go @@ -98,25 +98,9 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use } user := &portainer.User{ - Username: username, - Role: portainer.StandardUserRole, - PortainerAuthorizations: map[portainer.Authorization]bool{ - portainer.OperationPortainerDockerHubInspect: true, - portainer.OperationPortainerEndpointGroupList: true, - portainer.OperationPortainerEndpointList: true, - portainer.OperationPortainerEndpointInspect: true, - portainer.OperationPortainerEndpointExtensionAdd: true, - portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, - portainer.OperationPortainerMOTD: true, - portainer.OperationPortainerRegistryList: true, - portainer.OperationPortainerRegistryInspect: true, - portainer.OperationPortainerTeamList: true, - portainer.OperationPortainerTemplateList: true, - portainer.OperationPortainerTemplateInspect: true, - portainer.OperationPortainerUserList: true, - portainer.OperationPortainerUserMemberships: true, - }, + Username: username, + Role: portainer.StandardUserRole, + PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(), } err = handler.UserService.CreateUser(user) @@ -134,59 +118,14 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError { tokenData := &portainer.TokenData{ - ID: user.ID, - Username: user.Username, - Role: user.Role, - PortainerAuthorizations: user.PortainerAuthorizations, + ID: user.ID, + Username: user.Username, + Role: user.Role, } - _, err := handler.ExtensionService.Extension(portainer.RBACExtension) - if err == portainer.ErrObjectNotFound { - return handler.persistAndWriteToken(w, tokenData) - } else if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err} - } - - endpointAuthorizations, err := handler.getAuthorizations(user) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve authorizations associated to the user", err} - } - tokenData.EndpointAuthorizations = endpointAuthorizations - return handler.persistAndWriteToken(w, tokenData) } -func (handler *Handler) getAuthorizations(user *portainer.User) (portainer.EndpointAuthorizations, error) { - endpointAuthorizations := portainer.EndpointAuthorizations{} - if user.Role == portainer.AdministratorRole { - return endpointAuthorizations, nil - } - - userMemberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(user.ID) - if err != nil { - return endpointAuthorizations, err - } - - endpoints, err := handler.EndpointService.Endpoints() - if err != nil { - return endpointAuthorizations, err - } - - endpointGroups, err := handler.EndpointGroupService.EndpointGroups() - if err != nil { - return endpointAuthorizations, err - } - - roles, err := handler.RoleService.Roles() - if err != nil { - return endpointAuthorizations, err - } - - endpointAuthorizations = getUserEndpointAuthorizations(user, endpoints, endpointGroups, roles, userMemberships) - - return endpointAuthorizations, nil -} - func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError { token, err := handler.JWTService.GenerateToken(tokenData) if err != nil { diff --git a/api/http/handler/auth/authenticate_oauth.go b/api/http/handler/auth/authenticate_oauth.go index b87a759e0..849859d9f 100644 --- a/api/http/handler/auth/authenticate_oauth.go +++ b/api/http/handler/auth/authenticate_oauth.go @@ -111,25 +111,9 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h if user == nil { user = &portainer.User{ - Username: username, - Role: portainer.StandardUserRole, - PortainerAuthorizations: map[portainer.Authorization]bool{ - portainer.OperationPortainerDockerHubInspect: true, - portainer.OperationPortainerEndpointGroupList: true, - portainer.OperationPortainerEndpointList: true, - portainer.OperationPortainerEndpointInspect: true, - portainer.OperationPortainerEndpointExtensionAdd: true, - portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, - portainer.OperationPortainerMOTD: true, - portainer.OperationPortainerRegistryList: true, - portainer.OperationPortainerRegistryInspect: true, - portainer.OperationPortainerTeamList: true, - portainer.OperationPortainerTemplateList: true, - portainer.OperationPortainerTemplateInspect: true, - portainer.OperationPortainerUserList: true, - portainer.OperationPortainerUserMemberships: true, - }, + Username: username, + Role: portainer.StandardUserRole, + PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(), } err = handler.UserService.CreateUser(user) diff --git a/api/http/handler/auth/authorization.go b/api/http/handler/auth/authorization.go deleted file mode 100644 index 64f2e5cd8..000000000 --- a/api/http/handler/auth/authorization.go +++ /dev/null @@ -1,122 +0,0 @@ -package auth - -import portainer "github.com/portainer/portainer/api" - -func getUserEndpointAuthorizations(user *portainer.User, endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, roles []portainer.Role, userMemberships []portainer.TeamMembership) portainer.EndpointAuthorizations { - endpointAuthorizations := make(portainer.EndpointAuthorizations) - - groupUserAccessPolicies := map[portainer.EndpointGroupID]portainer.UserAccessPolicies{} - groupTeamAccessPolicies := map[portainer.EndpointGroupID]portainer.TeamAccessPolicies{} - for _, endpointGroup := range endpointGroups { - groupUserAccessPolicies[endpointGroup.ID] = endpointGroup.UserAccessPolicies - groupTeamAccessPolicies[endpointGroup.ID] = endpointGroup.TeamAccessPolicies - } - - for _, endpoint := range endpoints { - authorizations := getAuthorizationsFromUserEndpointPolicy(user, &endpoint, roles) - if len(authorizations) > 0 { - endpointAuthorizations[endpoint.ID] = authorizations - continue - } - - authorizations = getAuthorizationsFromUserEndpointGroupPolicy(user, &endpoint, roles, groupUserAccessPolicies) - if len(authorizations) > 0 { - endpointAuthorizations[endpoint.ID] = authorizations - continue - } - - authorizations = getAuthorizationsFromTeamEndpointPolicies(userMemberships, &endpoint, roles) - if len(authorizations) > 0 { - endpointAuthorizations[endpoint.ID] = authorizations - continue - } - - endpointAuthorizations[endpoint.ID] = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies) - } - - return endpointAuthorizations -} - -func getAuthorizationsFromUserEndpointPolicy(user *portainer.User, endpoint *portainer.Endpoint, roles []portainer.Role) portainer.Authorizations { - policyRoles := make([]portainer.RoleID, 0) - - policy, ok := endpoint.UserAccessPolicies[user.ID] - if ok { - policyRoles = append(policyRoles, policy.RoleID) - } - - return getAuthorizationsFromRoles(policyRoles, roles) -} - -func getAuthorizationsFromUserEndpointGroupPolicy(user *portainer.User, endpoint *portainer.Endpoint, roles []portainer.Role, groupAccessPolicies map[portainer.EndpointGroupID]portainer.UserAccessPolicies) portainer.Authorizations { - policyRoles := make([]portainer.RoleID, 0) - - policy, ok := groupAccessPolicies[endpoint.GroupID][user.ID] - if ok { - policyRoles = append(policyRoles, policy.RoleID) - } - - return getAuthorizationsFromRoles(policyRoles, roles) -} - -func getAuthorizationsFromTeamEndpointPolicies(memberships []portainer.TeamMembership, endpoint *portainer.Endpoint, roles []portainer.Role) portainer.Authorizations { - policyRoles := make([]portainer.RoleID, 0) - - for _, membership := range memberships { - policy, ok := endpoint.TeamAccessPolicies[membership.TeamID] - if ok { - policyRoles = append(policyRoles, policy.RoleID) - } - } - - return getAuthorizationsFromRoles(policyRoles, roles) -} - -func getAuthorizationsFromTeamEndpointGroupPolicies(memberships []portainer.TeamMembership, endpoint *portainer.Endpoint, roles []portainer.Role, groupAccessPolicies map[portainer.EndpointGroupID]portainer.TeamAccessPolicies) portainer.Authorizations { - policyRoles := make([]portainer.RoleID, 0) - - for _, membership := range memberships { - policy, ok := groupAccessPolicies[endpoint.GroupID][membership.TeamID] - if ok { - policyRoles = append(policyRoles, policy.RoleID) - } - } - - return getAuthorizationsFromRoles(policyRoles, roles) -} - -func getAuthorizationsFromRoles(roleIdentifiers []portainer.RoleID, roles []portainer.Role) portainer.Authorizations { - var roleAuthorizations []portainer.Authorizations - for _, id := range roleIdentifiers { - for _, role := range roles { - if role.ID == id { - roleAuthorizations = append(roleAuthorizations, role.Authorizations) - break - } - } - } - - processedAuthorizations := make(portainer.Authorizations) - if len(roleAuthorizations) > 0 { - processedAuthorizations = roleAuthorizations[0] - for idx, authorizations := range roleAuthorizations { - if idx == 0 { - continue - } - processedAuthorizations = mergeAuthorizations(processedAuthorizations, authorizations) - } - } - - return processedAuthorizations -} - -func mergeAuthorizations(a, b portainer.Authorizations) portainer.Authorizations { - c := make(map[portainer.Authorization]bool) - - for k := range b { - if _, ok := a[k]; ok { - c[k] = true - } - } - return c -} diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go index 92dbc9037..c21c3a452 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update.go +++ b/api/http/handler/endpointgroups/endpointgroup_update.go @@ -53,12 +53,15 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque endpointGroup.Tags = payload.Tags } + updateAuthorizations := false if payload.UserAccessPolicies != nil { endpointGroup.UserAccessPolicies = payload.UserAccessPolicies + updateAuthorizations = true } if payload.TeamAccessPolicies != nil { endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies + updateAuthorizations = true } err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup) @@ -66,5 +69,12 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err} } + if updateAuthorizations { + err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&payload.UserAccessPolicies, &payload.TeamAccessPolicies) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + } + return response.JSON(w, endpointGroup) } diff --git a/api/http/handler/endpointgroups/handler.go b/api/http/handler/endpointgroups/handler.go index fa10d92c9..785a7adfc 100644 --- a/api/http/handler/endpointgroups/handler.go +++ b/api/http/handler/endpointgroups/handler.go @@ -14,6 +14,7 @@ type Handler struct { *mux.Router EndpointService portainer.EndpointService EndpointGroupService portainer.EndpointGroupService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage endpoint group operations. diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index f62ee638b..9ff91113c 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -76,12 +76,15 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * endpoint.Tags = payload.Tags } + updateAuthorizations := false if payload.UserAccessPolicies != nil { endpoint.UserAccessPolicies = payload.UserAccessPolicies + updateAuthorizations = true } if payload.TeamAccessPolicies != nil { endpoint.TeamAccessPolicies = payload.TeamAccessPolicies + updateAuthorizations = true } if payload.Status != nil { @@ -173,5 +176,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err} } + if updateAuthorizations { + err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&payload.UserAccessPolicies, &payload.TeamAccessPolicies) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + } + return response.JSON(w, endpoint) } diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 17d0012bb..908df24fa 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -37,6 +37,7 @@ type Handler struct { JobService portainer.JobService ReverseTunnelService portainer.ReverseTunnelService SettingsService portainer.SettingsService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage endpoint operations. diff --git a/api/http/handler/extensions/handler.go b/api/http/handler/extensions/handler.go index a7ab38feb..9562d695c 100644 --- a/api/http/handler/extensions/handler.go +++ b/api/http/handler/extensions/handler.go @@ -17,6 +17,7 @@ type Handler struct { EndpointGroupService portainer.EndpointGroupService EndpointService portainer.EndpointService RegistryService portainer.RegistryService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage extension operations. diff --git a/api/http/handler/extensions/upgrade.go b/api/http/handler/extensions/upgrade.go index 7e3203412..da1676df8 100644 --- a/api/http/handler/extensions/upgrade.go +++ b/api/http/handler/extensions/upgrade.go @@ -1,6 +1,8 @@ package extensions -import portainer "github.com/portainer/portainer/api" +import ( + portainer "github.com/portainer/portainer/api" +) func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) { tmp := policies[key] @@ -34,6 +36,10 @@ func (handler *Handler) upgradeRBACData() error { return err } + err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpointGroup.UserAccessPolicies, &endpointGroup.TeamAccessPolicies) + if err != nil { + return err + } } endpoints, err := handler.EndpointService.Endpoints() @@ -54,6 +60,11 @@ func (handler *Handler) upgradeRBACData() error { if err != nil { return err } + + err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpoint.UserAccessPolicies, &endpoint.TeamAccessPolicies) + if err != nil { + return err + } } return nil } diff --git a/api/http/handler/users/admin_init.go b/api/http/handler/users/admin_init.go index 044bc2876..a57e95f62 100644 --- a/api/http/handler/users/admin_init.go +++ b/api/http/handler/users/admin_init.go @@ -43,25 +43,9 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe } user := &portainer.User{ - Username: payload.Username, - Role: portainer.AdministratorRole, - PortainerAuthorizations: map[portainer.Authorization]bool{ - portainer.OperationPortainerDockerHubInspect: true, - portainer.OperationPortainerEndpointGroupList: true, - portainer.OperationPortainerEndpointList: true, - portainer.OperationPortainerEndpointInspect: true, - portainer.OperationPortainerEndpointExtensionAdd: true, - portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, - portainer.OperationPortainerMOTD: true, - portainer.OperationPortainerRegistryList: true, - portainer.OperationPortainerRegistryInspect: true, - portainer.OperationPortainerTeamList: true, - portainer.OperationPortainerTemplateList: true, - portainer.OperationPortainerTemplateInspect: true, - portainer.OperationPortainerUserList: true, - portainer.OperationPortainerUserMemberships: true, - }, + Username: payload.Username, + Role: portainer.AdministratorRole, + PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(), } user.Password, err = handler.CryptoService.Hash(payload.Password) diff --git a/api/http/handler/users/user_create.go b/api/http/handler/users/user_create.go index ab89cf1ff..582ca084d 100644 --- a/api/http/handler/users/user_create.go +++ b/api/http/handler/users/user_create.go @@ -58,25 +58,9 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http } user = &portainer.User{ - Username: payload.Username, - Role: portainer.UserRole(payload.Role), - PortainerAuthorizations: map[portainer.Authorization]bool{ - portainer.OperationPortainerDockerHubInspect: true, - portainer.OperationPortainerEndpointGroupList: true, - portainer.OperationPortainerEndpointList: true, - portainer.OperationPortainerEndpointInspect: true, - portainer.OperationPortainerEndpointExtensionAdd: true, - portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, - portainer.OperationPortainerMOTD: true, - portainer.OperationPortainerRegistryList: true, - portainer.OperationPortainerRegistryInspect: true, - portainer.OperationPortainerTeamList: true, - portainer.OperationPortainerTemplateList: true, - portainer.OperationPortainerTemplateInspect: true, - portainer.OperationPortainerUserList: true, - portainer.OperationPortainerUserMemberships: true, - }, + Username: payload.Username, + Role: portainer.UserRole(payload.Role), + PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(), } settings, err := handler.SettingsService.Settings() diff --git a/api/http/handler/users/user_inspect.go b/api/http/handler/users/user_inspect.go index f0723bd49..4c5954283 100644 --- a/api/http/handler/users/user_inspect.go +++ b/api/http/handler/users/user_inspect.go @@ -3,6 +3,8 @@ package users import ( "net/http" + "github.com/portainer/portainer/api/http/security" + httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" @@ -16,6 +18,15 @@ func (handler *Handler) userInspect(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusBadRequest, "Invalid user identifier route variable", err} } + securityContext, err := security.RetrieveRestrictedRequestContext(r) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err} + } + + if !securityContext.IsAdmin && securityContext.UserID != portainer.UserID(userID) { + return &httperror.HandlerError{http.StatusForbidden, "Permission denied inspect user", portainer.ErrResourceAccessDenied} + } + user, err := handler.UserService.User(portainer.UserID(userID)) if err == portainer.ErrObjectNotFound { return &httperror.HandlerError{http.StatusNotFound, "Unable to find a user with the specified identifier inside the database", err} diff --git a/api/http/proxy/docker_transport.go b/api/http/proxy/docker_transport.go index a3e3d99f8..8c1a2e28c 100644 --- a/api/http/proxy/docker_transport.go +++ b/api/http/proxy/docker_transport.go @@ -19,6 +19,7 @@ type ( dockerTransport *http.Transport enableSignature bool ResourceControlService portainer.ResourceControlService + UserService portainer.UserService TeamMembershipService portainer.TeamMembershipService RegistryService portainer.RegistryService DockerHubService portainer.DockerHubService @@ -498,7 +499,12 @@ func (p *proxyTransport) createOperationContext(request *http.Request) (*restric if tokenData.Role != portainer.AdministratorRole { operationContext.isAdmin = false - _, ok := tokenData.EndpointAuthorizations[p.endpointIdentifier][portainer.EndpointResourcesAccess] + user, err := p.UserService.User(operationContext.userID) + if err != nil { + return nil, err + } + + _, ok := user.EndpointAuthorizations[p.endpointIdentifier][portainer.EndpointResourcesAccess] if ok { operationContext.endpointResourceAccess = true } diff --git a/api/http/proxy/factory.go b/api/http/proxy/factory.go index 0774e7f14..a1f4e1e32 100644 --- a/api/http/proxy/factory.go +++ b/api/http/proxy/factory.go @@ -16,6 +16,7 @@ const AzureAPIBaseURL = "https://management.azure.com" // proxyFactory is a factory to create reverse proxies to Docker endpoints type proxyFactory struct { ResourceControlService portainer.ResourceControlService + UserService portainer.UserService TeamMembershipService portainer.TeamMembershipService SettingsService portainer.SettingsService RegistryService portainer.RegistryService @@ -70,6 +71,7 @@ func (factory *proxyFactory) createDockerReverseProxy(u *url.URL, endpoint *port transport := &proxyTransport{ enableSignature: enableSignature, ResourceControlService: factory.ResourceControlService, + UserService: factory.UserService, TeamMembershipService: factory.TeamMembershipService, SettingsService: factory.SettingsService, RegistryService: factory.RegistryService, diff --git a/api/http/proxy/factory_local.go b/api/http/proxy/factory_local.go index 1e461ab1a..37b7f5401 100644 --- a/api/http/proxy/factory_local.go +++ b/api/http/proxy/factory_local.go @@ -13,6 +13,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp transport := &proxyTransport{ enableSignature: false, ResourceControlService: factory.ResourceControlService, + UserService: factory.UserService, TeamMembershipService: factory.TeamMembershipService, SettingsService: factory.SettingsService, RegistryService: factory.RegistryService, diff --git a/api/http/proxy/factory_local_windows.go b/api/http/proxy/factory_local_windows.go index c90d09643..0c6726a8d 100644 --- a/api/http/proxy/factory_local_windows.go +++ b/api/http/proxy/factory_local_windows.go @@ -3,10 +3,11 @@ package proxy import ( - "github.com/Microsoft/go-winio" "net" "net/http" + "github.com/Microsoft/go-winio" + portainer "github.com/portainer/portainer/api" ) @@ -15,6 +16,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp transport := &proxyTransport{ enableSignature: false, ResourceControlService: factory.ResourceControlService, + UserService: factory.UserService, TeamMembershipService: factory.TeamMembershipService, SettingsService: factory.SettingsService, RegistryService: factory.RegistryService, diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index 6d0311cfa..5d87f2393 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -31,6 +31,7 @@ type ( // ManagerParams represents the required parameters to create a new Manager instance. ManagerParams struct { ResourceControlService portainer.ResourceControlService + UserService portainer.UserService TeamMembershipService portainer.TeamMembershipService SettingsService portainer.SettingsService RegistryService portainer.RegistryService @@ -48,6 +49,7 @@ func NewManager(parameters *ManagerParams) *Manager { legacyExtensionProxies: cmap.New(), proxyFactory: &proxyFactory{ ResourceControlService: parameters.ResourceControlService, + UserService: parameters.UserService, TeamMembershipService: parameters.TeamMembershipService, SettingsService: parameters.SettingsService, RegistryService: parameters.RegistryService, diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go index ad2152c77..c4e6f5dcd 100644 --- a/api/http/security/bouncer.go +++ b/api/http/security/bouncer.go @@ -142,10 +142,15 @@ func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Reque return err } + user, err := bouncer.userService.User(tokenData.ID) + if err != nil { + return err + } + apiOperation := &portainer.APIOperationAuthorizationRequest{ Path: r.URL.String(), Method: r.Method, - Authorizations: tokenData.EndpointAuthorizations[endpoint.ID], + Authorizations: user.EndpointAuthorizations[endpoint.ID], } bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) @@ -208,10 +213,19 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler) return } + user, err := bouncer.userService.User(tokenData.ID) + if err != nil && err == portainer.ErrObjectNotFound { + httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized) + return + } else if err != nil { + httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err) + return + } + apiOperation := &portainer.APIOperationAuthorizationRequest{ Path: r.URL.String(), Method: r.Method, - Authorizations: tokenData.PortainerAuthorizations, + Authorizations: user.PortainerAuthorizations, } bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) @@ -281,7 +295,7 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized) return } else if err != nil { - httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve users from the database", err) + httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err) return } } else { diff --git a/api/http/server.go b/api/http/server.go index f058d2e00..5810f4b2b 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -84,6 +84,7 @@ type Server struct { func (server *Server) Start() error { proxyManagerParameters := &proxy.ManagerParams{ ResourceControlService: server.ResourceControlService, + UserService: server.UserService, TeamMembershipService: server.TeamMembershipService, SettingsService: server.SettingsService, RegistryService: server.RegistryService, @@ -93,6 +94,15 @@ func (server *Server) Start() error { } proxyManager := proxy.NewManager(proxyManagerParameters) + authorizationServiceParameters := &portainer.AuthorizationServiceParameters{ + EndpointService: server.EndpointService, + EndpointGroupService: server.EndpointGroupService, + RoleService: server.RoleService, + TeamMembershipService: server.TeamMembershipService, + UserService: server.UserService, + } + authorizationService := portainer.NewAuthorizationService(authorizationServiceParameters) + requestBouncerParameters := &security.RequestBouncerParams{ JWTService: server.JWTService, UserService: server.UserService, @@ -136,10 +146,12 @@ func (server *Server) Start() error { endpointHandler.JobService = server.JobService endpointHandler.ReverseTunnelService = server.ReverseTunnelService endpointHandler.SettingsService = server.SettingsService + endpointHandler.AuthorizationService = authorizationService var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer) endpointGroupHandler.EndpointGroupService = server.EndpointGroupService endpointGroupHandler.EndpointService = server.EndpointService + endpointGroupHandler.AuthorizationService = authorizationService var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer) endpointProxyHandler.EndpointService = server.EndpointService @@ -157,6 +169,7 @@ func (server *Server) Start() error { extensionHandler.EndpointGroupService = server.EndpointGroupService extensionHandler.EndpointService = server.EndpointService extensionHandler.RegistryService = server.RegistryService + extensionHandler.AuthorizationService = authorizationService var registryHandler = registries.NewHandler(requestBouncer) registryHandler.RegistryService = server.RegistryService diff --git a/api/jwt/jwt.go b/api/jwt/jwt.go index 9e18a4bbb..316495724 100644 --- a/api/jwt/jwt.go +++ b/api/jwt/jwt.go @@ -16,11 +16,9 @@ type Service struct { } type claims struct { - UserID int `json:"id"` - Username string `json:"username"` - Role int `json:"role"` - EndpointAuthorizations portainer.EndpointAuthorizations `json:"endpointAuthorizations"` - PortainerAuthorizations portainer.Authorizations `json:"portainerAuthorizations"` + UserID int `json:"id"` + Username string `json:"username"` + Role int `json:"role"` jwt.StandardClaims } @@ -40,12 +38,10 @@ func NewService() (*Service, error) { func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) { expireToken := time.Now().Add(time.Hour * 8).Unix() cl := claims{ - int(data.ID), - data.Username, - int(data.Role), - data.EndpointAuthorizations, - data.PortainerAuthorizations, - jwt.StandardClaims{ + UserID: int(data.ID), + Username: data.Username, + Role: int(data.Role), + StandardClaims: jwt.StandardClaims{ ExpiresAt: expireToken, }, } @@ -71,11 +67,9 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData, if err == nil && parsedToken != nil { if cl, ok := parsedToken.Claims.(*claims); ok && parsedToken.Valid { tokenData := &portainer.TokenData{ - ID: portainer.UserID(cl.UserID), - Username: cl.Username, - Role: portainer.UserRole(cl.Role), - EndpointAuthorizations: cl.EndpointAuthorizations, - PortainerAuthorizations: cl.PortainerAuthorizations, + ID: portainer.UserID(cl.UserID), + Username: cl.Username, + Role: portainer.UserRole(cl.Role), } return tokenData, nil } diff --git a/api/portainer.go b/api/portainer.go index d7961929e..438002d23 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -118,11 +118,12 @@ type ( // User represents a user account User struct { - ID UserID `json:"Id"` - Username string `json:"Username"` - Password string `json:"Password,omitempty"` - Role UserRole `json:"Role"` - PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` + ID UserID `json:"Id"` + Username string `json:"Username"` + Password string `json:"Password,omitempty"` + Role UserRole `json:"Role"` + PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` + EndpointAuthorizations EndpointAuthorizations `json:"EndpointAuthorizations"` } // UserID represents a user identifier @@ -160,11 +161,9 @@ type ( // TokenData represents the data embedded in a JWT token TokenData struct { - ID UserID - Username string - Role UserRole - EndpointAuthorizations EndpointAuthorizations - PortainerAuthorizations Authorizations + ID UserID + Username string + Role UserRole } // StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier) @@ -904,7 +903,7 @@ const ( // APIVersion is the version number of the Portainer API APIVersion = "1.22.0" // DBVersion is the version number of the Portainer database - DBVersion = 19 + DBVersion = 20 // AssetsServerURL represents the URL of the Portainer asset server AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com" // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved diff --git a/app/portainer/models/user.js b/app/portainer/models/user.js index e86be99f0..dbd31cfef 100644 --- a/app/portainer/models/user.js +++ b/app/portainer/models/user.js @@ -2,6 +2,8 @@ export function UserViewModel(data) { this.Id = data.Id; this.Username = data.Username; this.Role = data.Role; + this.EndpointAuthorizations = data.EndpointAuthorizations; + this.PortainerAuthorizations = data.PortainerAuthorizations; if (data.Role === 1) { this.RoleName = 'administrator'; } else { diff --git a/app/portainer/services/authentication.js b/app/portainer/services/authentication.js index d42c284fb..2f0181c87 100644 --- a/app/portainer/services/authentication.js +++ b/app/portainer/services/authentication.js @@ -1,7 +1,7 @@ angular.module('portainer.app') .factory('Authentication', [ -'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', -function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider) { +'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService', +function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) { 'use strict'; var service = {}; @@ -15,6 +15,7 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage service.getUserDetails = getUserDetails; service.isAdmin = isAdmin; service.hasAuthorizations = hasAuthorizations; + service.retrievePermissions = retrievePermissions; function init() { var jwt = LocalStorage.getJWT(); @@ -53,14 +54,20 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage return user; } + function retrievePermissions() { + return UserService.user(user.ID) + .then((data) => { + user.endpointAuthorizations = data.EndpointAuthorizations; + user.portainerAuthorizations = data.PortainerAuthorizations; + }); + } + function setUser(jwt) { LocalStorage.storeJWT(jwt); var tokenPayload = jwtHelper.decodeToken(jwt); user.username = tokenPayload.username; user.ID = tokenPayload.id; user.role = tokenPayload.role; - user.endpointAuthorizations = tokenPayload.endpointAuthorizations; - user.portainerAuthorizations = tokenPayload.portainerAuthorizations; } function isAdmin() { diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index 7bd3295cb..d79889ec2 100644 --- a/app/portainer/views/auth/authController.js +++ b/app/portainer/views/auth/authController.js @@ -29,12 +29,21 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us } } + function permissionsError() { + $scope.state.permissionsError = true; + Authentication.logout(); + $scope.state.AuthenticationError = 'Unable to retrieve permissions.' + $scope.state.loginInProgress = false; + return Promise.reject(); + } + $scope.authenticateUser = function() { var username = $scope.formValues.Username; var password = $scope.formValues.Password; $scope.state.loginInProgress = true; Authentication.login(username, password) + .then(() => Authentication.retrievePermissions().catch(permissionsError)) .then(function success() { return retrieveAndSaveEnabledExtensions(); }) @@ -42,6 +51,9 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us checkForEndpoints(); }) .catch(function error() { + if ($scope.state.permissionsError) { + return; + } SettingsService.publicSettings() .then(function success(settings) { if (settings.AuthenticationMethod === 1) { @@ -166,6 +178,7 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us function oAuthLogin(code) { return Authentication.OAuthLogin(code) + .then(() => Authentication.retrievePermissions().catch(permissionsError)) .then(function success() { return retrieveAndSaveEnabledExtensions(); }) @@ -173,6 +186,9 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us URLHelper.cleanParameters(); }) .catch(function error() { + if ($scope.state.permissionsError) { + return; + } $scope.state.AuthenticationError = 'Unable to login via OAuth'; $scope.state.isInOAuthProcess = false; });