feat(api): relocate authorizations outside of JWT (#3079)

* feat(api): relocate authorizations outside of JWT

* fix(api): update user authorization after enabling the RBAC extension

* feat(api): add PortainerEndpointList operation in the default portainer authorizations

* feat(auth): retrieve authorization from API instead of JWT

* refactor(auth): move permissions retrieval to function

* refactor(api): document authorizations methods
pull/3148/head
Anthony Lapenna 2019-09-10 10:58:26 +12:00 committed by GitHub
parent 7ebb3e62dd
commit 7d76bc89e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 472 additions and 303 deletions

266
api/authorizations.go Normal file
View File

@ -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
}

View File

@ -124,8 +124,10 @@ func (store *Store) MigrateData() error {
ExtensionService: store.ExtensionService, ExtensionService: store.ExtensionService,
RegistryService: store.RegistryService, RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService, ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
SettingsService: store.SettingsService, SettingsService: store.SettingsService,
StackService: store.StackService, StackService: store.StackService,
TeamMembershipService: store.TeamMembershipService,
TemplateService: store.TemplateService, TemplateService: store.TemplateService,
UserService: store.UserService, UserService: store.UserService,
VersionService: store.VersionService, VersionService: store.VersionService,

View File

@ -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
}

View File

@ -8,8 +8,10 @@ import (
"github.com/portainer/portainer/api/bolt/extension" "github.com/portainer/portainer/api/bolt/extension"
"github.com/portainer/portainer/api/bolt/registry" "github.com/portainer/portainer/api/bolt/registry"
"github.com/portainer/portainer/api/bolt/resourcecontrol" "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/settings"
"github.com/portainer/portainer/api/bolt/stack" "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/template"
"github.com/portainer/portainer/api/bolt/user" "github.com/portainer/portainer/api/bolt/user"
"github.com/portainer/portainer/api/bolt/version" "github.com/portainer/portainer/api/bolt/version"
@ -25,8 +27,10 @@ type (
extensionService *extension.Service extensionService *extension.Service
registryService *registry.Service registryService *registry.Service
resourceControlService *resourcecontrol.Service resourceControlService *resourcecontrol.Service
roleService *role.Service
settingsService *settings.Service settingsService *settings.Service
stackService *stack.Service stackService *stack.Service
teamMembershipService *teammembership.Service
templateService *template.Service templateService *template.Service
userService *user.Service userService *user.Service
versionService *version.Service versionService *version.Service
@ -42,8 +46,10 @@ type (
ExtensionService *extension.Service ExtensionService *extension.Service
RegistryService *registry.Service RegistryService *registry.Service
ResourceControlService *resourcecontrol.Service ResourceControlService *resourcecontrol.Service
RoleService *role.Service
SettingsService *settings.Service SettingsService *settings.Service
StackService *stack.Service StackService *stack.Service
TeamMembershipService *teammembership.Service
TemplateService *template.Service TemplateService *template.Service
UserService *user.Service UserService *user.Service
VersionService *version.Service VersionService *version.Service
@ -61,7 +67,9 @@ func NewMigrator(parameters *Parameters) *Migrator {
extensionService: parameters.ExtensionService, extensionService: parameters.ExtensionService,
registryService: parameters.RegistryService, registryService: parameters.RegistryService,
resourceControlService: parameters.ResourceControlService, resourceControlService: parameters.ResourceControlService,
roleService: parameters.RoleService,
settingsService: parameters.SettingsService, settingsService: parameters.SettingsService,
teamMembershipService: parameters.TeamMembershipService,
templateService: parameters.TemplateService, templateService: parameters.TemplateService,
stackService: parameters.StackService, stackService: parameters.StackService,
userService: parameters.UserService, 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) return m.versionService.StoreDBVersion(portainer.DBVersion)
} }

View File

@ -638,23 +638,7 @@ func main() {
Username: "admin", Username: "admin",
Role: portainer.AdministratorRole, Role: portainer.AdministratorRole,
Password: adminPasswordHash, Password: adminPasswordHash,
PortainerAuthorizations: map[portainer.Authorization]bool{ PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
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,
},
} }
err := store.UserService.CreateUser(user) err := store.UserService.CreateUser(user)
if err != nil { if err != nil {

View File

@ -100,23 +100,7 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
user := &portainer.User{ user := &portainer.User{
Username: username, Username: username,
Role: portainer.StandardUserRole, Role: portainer.StandardUserRole,
PortainerAuthorizations: map[portainer.Authorization]bool{ PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
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,
},
} }
err = handler.UserService.CreateUser(user) err = handler.UserService.CreateUser(user)
@ -137,56 +121,11 @@ func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User)
ID: user.ID, ID: user.ID,
Username: user.Username, Username: user.Username,
Role: user.Role, Role: user.Role,
PortainerAuthorizations: user.PortainerAuthorizations,
} }
_, 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) 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 { func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {
token, err := handler.JWTService.GenerateToken(tokenData) token, err := handler.JWTService.GenerateToken(tokenData)
if err != nil { if err != nil {

View File

@ -113,23 +113,7 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
user = &portainer.User{ user = &portainer.User{
Username: username, Username: username,
Role: portainer.StandardUserRole, Role: portainer.StandardUserRole,
PortainerAuthorizations: map[portainer.Authorization]bool{ PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
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,
},
} }
err = handler.UserService.CreateUser(user) err = handler.UserService.CreateUser(user)

View File

@ -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
}

View File

@ -53,12 +53,15 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
endpointGroup.Tags = payload.Tags endpointGroup.Tags = payload.Tags
} }
updateAuthorizations := false
if payload.UserAccessPolicies != nil { if payload.UserAccessPolicies != nil {
endpointGroup.UserAccessPolicies = payload.UserAccessPolicies endpointGroup.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
} }
if payload.TeamAccessPolicies != nil { if payload.TeamAccessPolicies != nil {
endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
} }
err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup) 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} 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) return response.JSON(w, endpointGroup)
} }

View File

@ -14,6 +14,7 @@ type Handler struct {
*mux.Router *mux.Router
EndpointService portainer.EndpointService EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService EndpointGroupService portainer.EndpointGroupService
AuthorizationService *portainer.AuthorizationService
} }
// NewHandler creates a handler to manage endpoint group operations. // NewHandler creates a handler to manage endpoint group operations.

View File

@ -76,12 +76,15 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.Tags = payload.Tags endpoint.Tags = payload.Tags
} }
updateAuthorizations := false
if payload.UserAccessPolicies != nil { if payload.UserAccessPolicies != nil {
endpoint.UserAccessPolicies = payload.UserAccessPolicies endpoint.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
} }
if payload.TeamAccessPolicies != nil { if payload.TeamAccessPolicies != nil {
endpoint.TeamAccessPolicies = payload.TeamAccessPolicies endpoint.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
} }
if payload.Status != nil { 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} 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) return response.JSON(w, endpoint)
} }

View File

@ -37,6 +37,7 @@ type Handler struct {
JobService portainer.JobService JobService portainer.JobService
ReverseTunnelService portainer.ReverseTunnelService ReverseTunnelService portainer.ReverseTunnelService
SettingsService portainer.SettingsService SettingsService portainer.SettingsService
AuthorizationService *portainer.AuthorizationService
} }
// NewHandler creates a handler to manage endpoint operations. // NewHandler creates a handler to manage endpoint operations.

View File

@ -17,6 +17,7 @@ type Handler struct {
EndpointGroupService portainer.EndpointGroupService EndpointGroupService portainer.EndpointGroupService
EndpointService portainer.EndpointService EndpointService portainer.EndpointService
RegistryService portainer.RegistryService RegistryService portainer.RegistryService
AuthorizationService *portainer.AuthorizationService
} }
// NewHandler creates a handler to manage extension operations. // NewHandler creates a handler to manage extension operations.

View File

@ -1,6 +1,8 @@
package extensions package extensions
import portainer "github.com/portainer/portainer/api" import (
portainer "github.com/portainer/portainer/api"
)
func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) { func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) {
tmp := policies[key] tmp := policies[key]
@ -34,6 +36,10 @@ func (handler *Handler) upgradeRBACData() error {
return err return err
} }
err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpointGroup.UserAccessPolicies, &endpointGroup.TeamAccessPolicies)
if err != nil {
return err
}
} }
endpoints, err := handler.EndpointService.Endpoints() endpoints, err := handler.EndpointService.Endpoints()
@ -54,6 +60,11 @@ func (handler *Handler) upgradeRBACData() error {
if err != nil { if err != nil {
return err return err
} }
err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpoint.UserAccessPolicies, &endpoint.TeamAccessPolicies)
if err != nil {
return err
}
} }
return nil return nil
} }

View File

@ -45,23 +45,7 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe
user := &portainer.User{ user := &portainer.User{
Username: payload.Username, Username: payload.Username,
Role: portainer.AdministratorRole, Role: portainer.AdministratorRole,
PortainerAuthorizations: map[portainer.Authorization]bool{ PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
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,
},
} }
user.Password, err = handler.CryptoService.Hash(payload.Password) user.Password, err = handler.CryptoService.Hash(payload.Password)

View File

@ -60,23 +60,7 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
user = &portainer.User{ user = &portainer.User{
Username: payload.Username, Username: payload.Username,
Role: portainer.UserRole(payload.Role), Role: portainer.UserRole(payload.Role),
PortainerAuthorizations: map[portainer.Authorization]bool{ PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
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,
},
} }
settings, err := handler.SettingsService.Settings() settings, err := handler.SettingsService.Settings()

View File

@ -3,6 +3,8 @@ package users
import ( import (
"net/http" "net/http"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "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} 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)) user, err := handler.UserService.User(portainer.UserID(userID))
if err == portainer.ErrObjectNotFound { if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a user with the specified identifier inside the database", err} return &httperror.HandlerError{http.StatusNotFound, "Unable to find a user with the specified identifier inside the database", err}

View File

@ -19,6 +19,7 @@ type (
dockerTransport *http.Transport dockerTransport *http.Transport
enableSignature bool enableSignature bool
ResourceControlService portainer.ResourceControlService ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService TeamMembershipService portainer.TeamMembershipService
RegistryService portainer.RegistryService RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService DockerHubService portainer.DockerHubService
@ -498,7 +499,12 @@ func (p *proxyTransport) createOperationContext(request *http.Request) (*restric
if tokenData.Role != portainer.AdministratorRole { if tokenData.Role != portainer.AdministratorRole {
operationContext.isAdmin = false 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 { if ok {
operationContext.endpointResourceAccess = true operationContext.endpointResourceAccess = true
} }

View File

@ -16,6 +16,7 @@ const AzureAPIBaseURL = "https://management.azure.com"
// proxyFactory is a factory to create reverse proxies to Docker endpoints // proxyFactory is a factory to create reverse proxies to Docker endpoints
type proxyFactory struct { type proxyFactory struct {
ResourceControlService portainer.ResourceControlService ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService TeamMembershipService portainer.TeamMembershipService
SettingsService portainer.SettingsService SettingsService portainer.SettingsService
RegistryService portainer.RegistryService RegistryService portainer.RegistryService
@ -70,6 +71,7 @@ func (factory *proxyFactory) createDockerReverseProxy(u *url.URL, endpoint *port
transport := &proxyTransport{ transport := &proxyTransport{
enableSignature: enableSignature, enableSignature: enableSignature,
ResourceControlService: factory.ResourceControlService, ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService, TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService, SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService, RegistryService: factory.RegistryService,

View File

@ -13,6 +13,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp
transport := &proxyTransport{ transport := &proxyTransport{
enableSignature: false, enableSignature: false,
ResourceControlService: factory.ResourceControlService, ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService, TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService, SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService, RegistryService: factory.RegistryService,

View File

@ -3,10 +3,11 @@
package proxy package proxy
import ( import (
"github.com/Microsoft/go-winio"
"net" "net"
"net/http" "net/http"
"github.com/Microsoft/go-winio"
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
) )
@ -15,6 +16,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp
transport := &proxyTransport{ transport := &proxyTransport{
enableSignature: false, enableSignature: false,
ResourceControlService: factory.ResourceControlService, ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService, TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService, SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService, RegistryService: factory.RegistryService,

View File

@ -31,6 +31,7 @@ type (
// ManagerParams represents the required parameters to create a new Manager instance. // ManagerParams represents the required parameters to create a new Manager instance.
ManagerParams struct { ManagerParams struct {
ResourceControlService portainer.ResourceControlService ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService TeamMembershipService portainer.TeamMembershipService
SettingsService portainer.SettingsService SettingsService portainer.SettingsService
RegistryService portainer.RegistryService RegistryService portainer.RegistryService
@ -48,6 +49,7 @@ func NewManager(parameters *ManagerParams) *Manager {
legacyExtensionProxies: cmap.New(), legacyExtensionProxies: cmap.New(),
proxyFactory: &proxyFactory{ proxyFactory: &proxyFactory{
ResourceControlService: parameters.ResourceControlService, ResourceControlService: parameters.ResourceControlService,
UserService: parameters.UserService,
TeamMembershipService: parameters.TeamMembershipService, TeamMembershipService: parameters.TeamMembershipService,
SettingsService: parameters.SettingsService, SettingsService: parameters.SettingsService,
RegistryService: parameters.RegistryService, RegistryService: parameters.RegistryService,

View File

@ -142,10 +142,15 @@ func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Reque
return err return err
} }
user, err := bouncer.userService.User(tokenData.ID)
if err != nil {
return err
}
apiOperation := &portainer.APIOperationAuthorizationRequest{ apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(), Path: r.URL.String(),
Method: r.Method, Method: r.Method,
Authorizations: tokenData.EndpointAuthorizations[endpoint.ID], Authorizations: user.EndpointAuthorizations[endpoint.ID],
} }
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
@ -208,10 +213,19 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler)
return 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{ apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(), Path: r.URL.String(),
Method: r.Method, Method: r.Method,
Authorizations: tokenData.PortainerAuthorizations, Authorizations: user.PortainerAuthorizations,
} }
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) 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) httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return return
} else if err != nil { } 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 return
} }
} else { } else {

View File

@ -84,6 +84,7 @@ type Server struct {
func (server *Server) Start() error { func (server *Server) Start() error {
proxyManagerParameters := &proxy.ManagerParams{ proxyManagerParameters := &proxy.ManagerParams{
ResourceControlService: server.ResourceControlService, ResourceControlService: server.ResourceControlService,
UserService: server.UserService,
TeamMembershipService: server.TeamMembershipService, TeamMembershipService: server.TeamMembershipService,
SettingsService: server.SettingsService, SettingsService: server.SettingsService,
RegistryService: server.RegistryService, RegistryService: server.RegistryService,
@ -93,6 +94,15 @@ func (server *Server) Start() error {
} }
proxyManager := proxy.NewManager(proxyManagerParameters) 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{ requestBouncerParameters := &security.RequestBouncerParams{
JWTService: server.JWTService, JWTService: server.JWTService,
UserService: server.UserService, UserService: server.UserService,
@ -136,10 +146,12 @@ func (server *Server) Start() error {
endpointHandler.JobService = server.JobService endpointHandler.JobService = server.JobService
endpointHandler.ReverseTunnelService = server.ReverseTunnelService endpointHandler.ReverseTunnelService = server.ReverseTunnelService
endpointHandler.SettingsService = server.SettingsService endpointHandler.SettingsService = server.SettingsService
endpointHandler.AuthorizationService = authorizationService
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer) var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
endpointGroupHandler.EndpointService = server.EndpointService endpointGroupHandler.EndpointService = server.EndpointService
endpointGroupHandler.AuthorizationService = authorizationService
var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer) var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer)
endpointProxyHandler.EndpointService = server.EndpointService endpointProxyHandler.EndpointService = server.EndpointService
@ -157,6 +169,7 @@ func (server *Server) Start() error {
extensionHandler.EndpointGroupService = server.EndpointGroupService extensionHandler.EndpointGroupService = server.EndpointGroupService
extensionHandler.EndpointService = server.EndpointService extensionHandler.EndpointService = server.EndpointService
extensionHandler.RegistryService = server.RegistryService extensionHandler.RegistryService = server.RegistryService
extensionHandler.AuthorizationService = authorizationService
var registryHandler = registries.NewHandler(requestBouncer) var registryHandler = registries.NewHandler(requestBouncer)
registryHandler.RegistryService = server.RegistryService registryHandler.RegistryService = server.RegistryService

View File

@ -19,8 +19,6 @@ type claims struct {
UserID int `json:"id"` UserID int `json:"id"`
Username string `json:"username"` Username string `json:"username"`
Role int `json:"role"` Role int `json:"role"`
EndpointAuthorizations portainer.EndpointAuthorizations `json:"endpointAuthorizations"`
PortainerAuthorizations portainer.Authorizations `json:"portainerAuthorizations"`
jwt.StandardClaims jwt.StandardClaims
} }
@ -40,12 +38,10 @@ func NewService() (*Service, error) {
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) { func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
expireToken := time.Now().Add(time.Hour * 8).Unix() expireToken := time.Now().Add(time.Hour * 8).Unix()
cl := claims{ cl := claims{
int(data.ID), UserID: int(data.ID),
data.Username, Username: data.Username,
int(data.Role), Role: int(data.Role),
data.EndpointAuthorizations, StandardClaims: jwt.StandardClaims{
data.PortainerAuthorizations,
jwt.StandardClaims{
ExpiresAt: expireToken, ExpiresAt: expireToken,
}, },
} }
@ -74,8 +70,6 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData,
ID: portainer.UserID(cl.UserID), ID: portainer.UserID(cl.UserID),
Username: cl.Username, Username: cl.Username,
Role: portainer.UserRole(cl.Role), Role: portainer.UserRole(cl.Role),
EndpointAuthorizations: cl.EndpointAuthorizations,
PortainerAuthorizations: cl.PortainerAuthorizations,
} }
return tokenData, nil return tokenData, nil
} }

View File

@ -123,6 +123,7 @@ type (
Password string `json:"Password,omitempty"` Password string `json:"Password,omitempty"`
Role UserRole `json:"Role"` Role UserRole `json:"Role"`
PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"`
EndpointAuthorizations EndpointAuthorizations `json:"EndpointAuthorizations"`
} }
// UserID represents a user identifier // UserID represents a user identifier
@ -163,8 +164,6 @@ type (
ID UserID ID UserID
Username string Username string
Role UserRole Role UserRole
EndpointAuthorizations EndpointAuthorizations
PortainerAuthorizations Authorizations
} }
// StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier) // 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 is the version number of the Portainer API
APIVersion = "1.22.0" APIVersion = "1.22.0"
// DBVersion is the version number of the Portainer database // DBVersion is the version number of the Portainer database
DBVersion = 19 DBVersion = 20
// AssetsServerURL represents the URL of the Portainer asset server // AssetsServerURL represents the URL of the Portainer asset server
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com" AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved // MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved

View File

@ -2,6 +2,8 @@ export function UserViewModel(data) {
this.Id = data.Id; this.Id = data.Id;
this.Username = data.Username; this.Username = data.Username;
this.Role = data.Role; this.Role = data.Role;
this.EndpointAuthorizations = data.EndpointAuthorizations;
this.PortainerAuthorizations = data.PortainerAuthorizations;
if (data.Role === 1) { if (data.Role === 1) {
this.RoleName = 'administrator'; this.RoleName = 'administrator';
} else { } else {

View File

@ -1,7 +1,7 @@
angular.module('portainer.app') angular.module('portainer.app')
.factory('Authentication', [ .factory('Authentication', [
'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'Auth', 'OAuth', 'jwtHelper', 'LocalStorage', 'StateManager', 'EndpointProvider', 'UserService',
function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider) { function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManager, EndpointProvider, UserService) {
'use strict'; 'use strict';
var service = {}; var service = {};
@ -15,6 +15,7 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage
service.getUserDetails = getUserDetails; service.getUserDetails = getUserDetails;
service.isAdmin = isAdmin; service.isAdmin = isAdmin;
service.hasAuthorizations = hasAuthorizations; service.hasAuthorizations = hasAuthorizations;
service.retrievePermissions = retrievePermissions;
function init() { function init() {
var jwt = LocalStorage.getJWT(); var jwt = LocalStorage.getJWT();
@ -53,14 +54,20 @@ function AuthenticationFactory(Auth, OAuth, jwtHelper, LocalStorage, StateManage
return user; return user;
} }
function retrievePermissions() {
return UserService.user(user.ID)
.then((data) => {
user.endpointAuthorizations = data.EndpointAuthorizations;
user.portainerAuthorizations = data.PortainerAuthorizations;
});
}
function setUser(jwt) { function setUser(jwt) {
LocalStorage.storeJWT(jwt); LocalStorage.storeJWT(jwt);
var tokenPayload = jwtHelper.decodeToken(jwt); var tokenPayload = jwtHelper.decodeToken(jwt);
user.username = tokenPayload.username; user.username = tokenPayload.username;
user.ID = tokenPayload.id; user.ID = tokenPayload.id;
user.role = tokenPayload.role; user.role = tokenPayload.role;
user.endpointAuthorizations = tokenPayload.endpointAuthorizations;
user.portainerAuthorizations = tokenPayload.portainerAuthorizations;
} }
function isAdmin() { function isAdmin() {

View File

@ -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() { $scope.authenticateUser = function() {
var username = $scope.formValues.Username; var username = $scope.formValues.Username;
var password = $scope.formValues.Password; var password = $scope.formValues.Password;
$scope.state.loginInProgress = true; $scope.state.loginInProgress = true;
Authentication.login(username, password) Authentication.login(username, password)
.then(() => Authentication.retrievePermissions().catch(permissionsError))
.then(function success() { .then(function success() {
return retrieveAndSaveEnabledExtensions(); return retrieveAndSaveEnabledExtensions();
}) })
@ -42,6 +51,9 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us
checkForEndpoints(); checkForEndpoints();
}) })
.catch(function error() { .catch(function error() {
if ($scope.state.permissionsError) {
return;
}
SettingsService.publicSettings() SettingsService.publicSettings()
.then(function success(settings) { .then(function success(settings) {
if (settings.AuthenticationMethod === 1) { if (settings.AuthenticationMethod === 1) {
@ -166,6 +178,7 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us
function oAuthLogin(code) { function oAuthLogin(code) {
return Authentication.OAuthLogin(code) return Authentication.OAuthLogin(code)
.then(() => Authentication.retrievePermissions().catch(permissionsError))
.then(function success() { .then(function success() {
return retrieveAndSaveEnabledExtensions(); return retrieveAndSaveEnabledExtensions();
}) })
@ -173,6 +186,9 @@ function($async, $q, $scope, $state, $stateParams, $sanitize, Authentication, Us
URLHelper.cleanParameters(); URLHelper.cleanParameters();
}) })
.catch(function error() { .catch(function error() {
if ($scope.state.permissionsError) {
return;
}
$scope.state.AuthenticationError = 'Unable to login via OAuth'; $scope.state.AuthenticationError = 'Unable to login via OAuth';
$scope.state.isInOAuthProcess = false; $scope.state.isInOAuthProcess = false;
}); });