Compare commits
22 Commits
release/2.
...
feat/EE-56
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cda372e38 | ||
|
|
339a44e43d | ||
|
|
c3a62797d7 | ||
|
|
fbfe81b3a1 | ||
|
|
fba55db143 | ||
|
|
aa9400e9dc | ||
|
|
002649c81a | ||
|
|
204542f073 | ||
|
|
521cb19fbc | ||
|
|
290844ffee | ||
|
|
d87aa4ca23 | ||
|
|
dfb01215ad | ||
|
|
6a7343179f | ||
|
|
7ae5cb21cb | ||
|
|
e03ede1dfb | ||
|
|
0972ae534f | ||
|
|
9da73e1594 | ||
|
|
aaa9c0f096 | ||
|
|
5474bfae5b | ||
|
|
6d7a5da1f2 | ||
|
|
5c774141f4 | ||
|
|
3eebe3a08a |
@@ -34,10 +34,13 @@ func (store *Store) Init() error {
|
||||
AutoCreateUsers: true,
|
||||
TLSConfig: portainer.TLSConfiguration{},
|
||||
SearchSettings: []portainer.LDAPSearchSettings{
|
||||
portainer.LDAPSearchSettings{},
|
||||
{},
|
||||
},
|
||||
GroupSearchSettings: []portainer.LDAPGroupSearchSettings{
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
{},
|
||||
},
|
||||
AdminGroupSearchSettings: []portainer.LDAPGroupSearchSettings{
|
||||
{},
|
||||
},
|
||||
},
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package migrator
|
||||
|
||||
import "github.com/portainer/portainer/api"
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) updateSettingsToVersion13() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
@@ -10,7 +10,7 @@ func (m *Migrator) updateSettingsToVersion13() error {
|
||||
|
||||
legacySettings.LDAPSettings.AutoCreateUsers = false
|
||||
legacySettings.LDAPSettings.GroupSearchSettings = []portainer.LDAPGroupSearchSettings{
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
{},
|
||||
}
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
|
||||
@@ -21,7 +21,13 @@ func (m *Migrator) migrateDBVersionToDB32() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.updateVolumeResourceControlToDB32(); err != nil {
|
||||
err = m.updateAdminGroupSearchSettingsToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.updateVolumeResourceControlToDB32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -228,6 +234,17 @@ func findResourcesToUpdateForDB32(dockerID string, volumesData map[string]interf
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Migrator) updateAdminGroupSearchSettingsToDB32() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if legacySettings.LDAPSettings.AdminGroupSearchSettings == nil {
|
||||
legacySettings.LDAPSettings.AdminGroupSearchSettings = []portainer.LDAPGroupSearchSettings{}
|
||||
}
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
|
||||
func (m *Migrator) kubeconfigExpiryToDB32() error {
|
||||
settings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
|
||||
37
api/bolt/migrator/migrate_dbversion31_test.go
Normal file
37
api/bolt/migrator/migrate_dbversion31_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/internal"
|
||||
"github.com/portainer/portainer/api/bolt/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrateAdminGroupSearchSettings(t *testing.T) {
|
||||
databasePath := path.Join(t.TempDir(), "portainer-ee-mig-32.db")
|
||||
dbConn, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
assert.NoError(t, err, "failed to init DB connection")
|
||||
defer dbConn.Close()
|
||||
|
||||
internalDBConn := &internal.DbConnection{DB: dbConn}
|
||||
settingsService, err := settings.NewService(internalDBConn)
|
||||
assert.NoError(t, err, "failed to init settings service")
|
||||
settingsService.UpdateSettings(&portainer.Settings{})
|
||||
|
||||
m := &Migrator{
|
||||
db: dbConn,
|
||||
settingsService: settingsService,
|
||||
}
|
||||
err = m.updateAdminGroupSearchSettingsToDB32()
|
||||
assert.NoError(t, err, "failed to update settings")
|
||||
|
||||
updatedSettings, err := settingsService.Settings()
|
||||
assert.NoError(t, err, "failed to fetch updated settings")
|
||||
assert.NotNil(t, updatedSettings.LDAPSettings.AdminGroupSearchSettings)
|
||||
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
@@ -53,28 +54,33 @@ func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *ht
|
||||
var payload authenticatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||
}
|
||||
|
||||
settings, err := handler.DataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve settings from the database", Err: err}
|
||||
}
|
||||
|
||||
u, err := handler.DataStore.User().UserByUsername(payload.Username)
|
||||
if err != nil && err != bolterrors.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a user with the specified username from the database", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve a user with the specified username from the database", Err: err}
|
||||
}
|
||||
|
||||
if err == bolterrors.ErrObjectNotFound && (settings.AuthenticationMethod == portainer.AuthenticationInternal || settings.AuthenticationMethod == portainer.AuthenticationOAuth) {
|
||||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: httperrors.ErrUnauthorized}
|
||||
}
|
||||
|
||||
if settings.AuthenticationMethod == portainer.AuthenticationLDAP {
|
||||
if u == nil && settings.LDAPSettings.AutoCreateUsers {
|
||||
return handler.authenticateLDAPAndCreateUser(w, payload.Username, payload.Password, &settings.LDAPSettings)
|
||||
} else if u == nil && !settings.LDAPSettings.AutoCreateUsers {
|
||||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
|
||||
if u == nil {
|
||||
if settings.LDAPSettings.AutoCreateUsers || settings.LDAPSettings.AdminAutoPopulate {
|
||||
return handler.authenticateLDAPAndCreateUser(w, payload.Username, payload.Password, &settings.LDAPSettings)
|
||||
}
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusUnprocessableEntity,
|
||||
Message: "Invalid credentials",
|
||||
Err: httperrors.ErrUnauthorized,
|
||||
}
|
||||
}
|
||||
return handler.authenticateLDAP(w, u, payload.Password, &settings.LDAPSettings)
|
||||
}
|
||||
@@ -88,18 +94,57 @@ func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.
|
||||
return handler.authenticateInternal(w, user, password)
|
||||
}
|
||||
|
||||
if ldapSettings.AdminAutoPopulate {
|
||||
isLDAPAdmin, err := isLDAPAdmin(user.Username, handler.LDAPService, ldapSettings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Failed to search and match LDAP admin groups",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if isLDAPAdmin && user.Role != portainer.AdministratorRole {
|
||||
if err := handler.updateUserRole(user, portainer.AdministratorRole); err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusUnprocessableEntity,
|
||||
Message: "Failed to assign admin role to the user",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isLDAPAdmin && user.Role == portainer.AdministratorRole {
|
||||
if err := handler.updateUserRole(user, portainer.StandardUserRole); err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusUnprocessableEntity,
|
||||
Message: "Failed to remove admin role from the user",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = handler.addUserIntoTeams(user, ldapSettings)
|
||||
if err != nil {
|
||||
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
|
||||
}
|
||||
|
||||
err = handler.AuthorizationService.UpdateUsersAuthorizations()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Unable to update user authorizations",
|
||||
Err: err,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
}
|
||||
|
||||
func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portainer.User, password string) *httperror.HandlerError {
|
||||
err := handler.CryptoService.CompareHashAndData(user.Password, password)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: httperrors.ErrUnauthorized}
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
@@ -108,7 +153,7 @@ func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portai
|
||||
func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
|
||||
err := handler.LDAPService.AuthenticateUser(username, password, ldapSettings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: err}
|
||||
}
|
||||
|
||||
user := &portainer.User{
|
||||
@@ -116,9 +161,23 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
|
||||
Role: portainer.StandardUserRole,
|
||||
}
|
||||
|
||||
if ldapSettings.AdminAutoPopulate {
|
||||
isLDAPAdmin, err := isLDAPAdmin(username, handler.LDAPService, ldapSettings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Failed to search and match LDAP admin groups",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if isLDAPAdmin {
|
||||
user.Role = portainer.AdministratorRole
|
||||
}
|
||||
}
|
||||
|
||||
err = handler.DataStore.User().CreateUser(user)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user inside the database", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist user inside the database", Err: err}
|
||||
}
|
||||
|
||||
err = handler.addUserIntoTeams(user, ldapSettings)
|
||||
@@ -126,6 +185,16 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
|
||||
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
|
||||
}
|
||||
|
||||
err = handler.AuthorizationService.UpdateUsersAuthorizations()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Unable to update user authorizations",
|
||||
Err: err,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
}
|
||||
|
||||
@@ -148,7 +217,7 @@ func (handler *Handler) addUserIntoTeams(user *portainer.User, settings *portain
|
||||
return err
|
||||
}
|
||||
|
||||
userGroups, err := handler.LDAPService.GetUserGroups(user.Username, settings)
|
||||
userGroups, err := handler.LDAPService.GetUserGroups(user.Username, settings, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -181,6 +250,36 @@ func (handler *Handler) addUserIntoTeams(user *portainer.User, settings *portain
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLDAPAdmin(username string, ldapService portainer.LDAPService, ldapSettings *portainer.LDAPSettings) (bool, error) {
|
||||
//get groups the user belongs to
|
||||
userGroups, err := ldapService.GetUserGroups(username, ldapSettings, true)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to retrieve user groups from LDAP server")
|
||||
}
|
||||
|
||||
//convert the AdminGroups recorded in LDAP Settings to a map
|
||||
adminGroupsMap := make(map[string]bool)
|
||||
for _, adminGroup := range ldapSettings.AdminGroups {
|
||||
adminGroupsMap[adminGroup] = true
|
||||
}
|
||||
|
||||
//check if any of the user groups matches the admin group records
|
||||
for _, userGroup := range userGroups {
|
||||
if adminGroupsMap[userGroup] {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) updateUserRole(user *portainer.User, role portainer.UserRole) error {
|
||||
user.Role = role
|
||||
if err := handler.DataStore.User().UpdateUser(user.ID, user); err != nil {
|
||||
return errors.New("Unable to update user role inside the database")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func teamExists(teamName string, ldapGroups []string) bool {
|
||||
for _, group := range ldapGroups {
|
||||
if strings.ToLower(group) == strings.ToLower(teamName) {
|
||||
|
||||
34
api/http/handler/auth/authenticate_test.go
Normal file
34
api/http/handler/auth/authenticate_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsLDAPAdmin_Match(t *testing.T) {
|
||||
|
||||
ldapService := testhelpers.NewLDAPService()
|
||||
|
||||
mockLDAPSettings := &portainer.LDAPSettings{
|
||||
AdminGroups: []string{"manager", "stuff"},
|
||||
}
|
||||
|
||||
isLDAPAdmin, err := isLDAPAdmin("username", ldapService, mockLDAPSettings)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, isLDAPAdmin)
|
||||
}
|
||||
|
||||
func TestIsLDAPAdmin_NotMatch(t *testing.T) {
|
||||
ldapService := testhelpers.NewLDAPService()
|
||||
|
||||
mockLDAPSettings := &portainer.LDAPSettings{
|
||||
AdminGroups: []string{"admin", "manager"},
|
||||
}
|
||||
|
||||
isLDAPAdmin, err := isLDAPAdmin("username", ldapService, mockLDAPSettings)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, isLDAPAdmin)
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle authentication operations.
|
||||
@@ -21,6 +22,7 @@ type Handler struct {
|
||||
OAuthService portainer.OAuthService
|
||||
ProxyManager *proxy.Manager
|
||||
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
|
||||
AuthorizationService *authorization.Service
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage authentication operations.
|
||||
|
||||
@@ -26,6 +26,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
|
||||
h.Handle("/ldap/check",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.ldapCheck))).Methods(http.MethodPost)
|
||||
h.Handle("/ldap/admin-groups",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.ldapAdminGroups))).Methods(http.MethodPost)
|
||||
h.Handle("/ldap/test",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.ldapTestLogin))).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
@@ -14,38 +15,31 @@ type checkPayload struct {
|
||||
}
|
||||
|
||||
func (payload *checkPayload) Validate(r *http.Request) error {
|
||||
if len(payload.LDAPSettings.URL) == 0 {
|
||||
return errors.New("Invalid LDAP URL")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id LDAPCheck
|
||||
// @summary Test LDAP connectivity
|
||||
// @description Test LDAP connectivity using LDAP details
|
||||
// @description **Access policy**: administrator
|
||||
// @tags ldap
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @param body body checkPayload true "details"
|
||||
// @success 204 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 500 "Server error"
|
||||
// @router /ldap/check [post]
|
||||
// POST request on /ldap/check
|
||||
func (handler *Handler) ldapCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload checkPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||
}
|
||||
|
||||
settings := &payload.LDAPSettings
|
||||
|
||||
err = handler.prefillSettings(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to fetch default settings", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to fetch default settings", Err: err}
|
||||
}
|
||||
|
||||
err = handler.LDAPService.TestConnectivity(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to connect to LDAP server", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to connect to LDAP server", Err: err}
|
||||
}
|
||||
|
||||
return response.Empty(w)
|
||||
|
||||
60
api/http/handler/ldap/ldap_groups.go
Normal file
60
api/http/handler/ldap/ldap_groups.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type adminGroupsPayload struct {
|
||||
LDAPSettings portainer.LDAPSettings
|
||||
}
|
||||
|
||||
func (payload *adminGroupsPayload) Validate(r *http.Request) error {
|
||||
if len(payload.LDAPSettings.URL) == 0 {
|
||||
return errors.New("Invalid LDAP URL. Empty URL identified")
|
||||
}
|
||||
if len(payload.LDAPSettings.AdminGroupSearchSettings) == 0 {
|
||||
return errors.New("Invalid AdminGroupSearchSettings. When 'AdminAutoPopulate' is true, at least one search settings is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id LDAPAdminGroups
|
||||
// @summary Fetch LDAP admin groups
|
||||
// @description Fetch LDAP admin groups from LDAP server based on AdminGroupSearchSettings
|
||||
// @description **Access policy**: administrator
|
||||
// @tags ldap
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param body body adminGroupsPayload true "LDAPSettings"
|
||||
// @success 200 {array} string "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 500 "Server error"
|
||||
// @router /ldap/admin-groups [post]
|
||||
func (handler *Handler) ldapAdminGroups(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload adminGroupsPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||
}
|
||||
|
||||
settings := &payload.LDAPSettings
|
||||
|
||||
err = handler.prefillSettings(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to fetch default settings", Err: err}
|
||||
}
|
||||
|
||||
groups, err := handler.LDAPService.SearchAdminGroups(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to search admin groups", Err: err}
|
||||
}
|
||||
|
||||
return response.JSON(w, groups)
|
||||
}
|
||||
54
api/http/handler/ldap/ldap_test_login.go
Normal file
54
api/http/handler/ldap/ldap_test_login.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
)
|
||||
|
||||
type testLoginPayload struct {
|
||||
LDAPSettings portainer.LDAPSettings
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type testLoginResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
|
||||
func (payload *testLoginPayload) Validate(r *http.Request) error {
|
||||
if len(payload.LDAPSettings.URL) == 0 {
|
||||
return errors.New("Invalid LDAP URL")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// POST request on /ldap/test
|
||||
func (handler *Handler) ldapTestLogin(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload testLoginPayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
settings := &payload.LDAPSettings
|
||||
|
||||
err = handler.prefillSettings(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to fetch default settings", err}
|
||||
}
|
||||
|
||||
err = handler.LDAPService.AuthenticateUser(payload.Username, payload.Password, settings)
|
||||
if err != nil && err != httperrors.ErrUnauthorized {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to test user authorization", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, &testLoginResponse{Valid: err != httperrors.ErrUnauthorized})
|
||||
|
||||
}
|
||||
@@ -66,6 +66,20 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||
return errors.New("Invalid user session timeout")
|
||||
}
|
||||
}
|
||||
if payload.AuthenticationMethod != nil && *payload.AuthenticationMethod == 2 {
|
||||
if payload.LDAPSettings == nil {
|
||||
return errors.New("Invalid LDAP Configuration")
|
||||
}
|
||||
if payload.LDAPSettings.URL == "" {
|
||||
return errors.New("Invalid LDAP URL")
|
||||
}
|
||||
if payload.LDAPSettings.AdminAutoPopulate && len(payload.LDAPSettings.AdminGroupSearchSettings) == 0 {
|
||||
return errors.New("Missing Admin group Search settings. when AdminAutoPopulate is true, at least one settings is required")
|
||||
}
|
||||
if !payload.LDAPSettings.AdminAutoPopulate && len(payload.LDAPSettings.AdminGroups) > 0 {
|
||||
payload.LDAPSettings.AdminGroups = []string{}
|
||||
}
|
||||
}
|
||||
if payload.KubeconfigExpiry != nil {
|
||||
_, err := time.ParseDuration(*payload.KubeconfigExpiry)
|
||||
if err != nil {
|
||||
@@ -93,12 +107,12 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
var payload settingsUpdatePayload
|
||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||
}
|
||||
|
||||
settings, err := handler.DataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve the settings from the database", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to retrieve the settings from the database", Err: err}
|
||||
}
|
||||
|
||||
if payload.AuthenticationMethod != nil {
|
||||
@@ -151,7 +165,7 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
|
||||
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update snapshot interval", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to update snapshot interval", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +200,7 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
|
||||
err = handler.DataStore.Settings().UpdateSettings(settings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to persist settings changes inside the database", Err: err}
|
||||
}
|
||||
|
||||
return response.JSON(w, settings)
|
||||
@@ -211,7 +225,7 @@ func (handler *Handler) updateTLS(settings *portainer.Settings) *httperror.Handl
|
||||
settings.LDAPSettings.TLSConfig.TLSCACertPath = ""
|
||||
err := handler.FileService.DeleteTLSFiles(filesystem.LDAPStorePath)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove TLS files from disk", err}
|
||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to remove TLS files from disk", Err: err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -103,6 +103,7 @@ func (server *Server) Start() error {
|
||||
offlineGate := offlinegate.NewOfflineGate()
|
||||
|
||||
var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
|
||||
authHandler.AuthorizationService = server.AuthorizationService
|
||||
authHandler.DataStore = server.DataStore
|
||||
authHandler.CryptoService = server.CryptoService
|
||||
authHandler.JWTService = server.JWTService
|
||||
|
||||
42
api/internal/testhelpers/ldap_service.go
Normal file
42
api/internal/testhelpers/ldap_service.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package testhelpers
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type ldapService struct{}
|
||||
|
||||
// NewLDAPService creates new mock for portainer.LDAPService
|
||||
func NewLDAPService() *ldapService {
|
||||
return &ldapService{}
|
||||
}
|
||||
|
||||
// AuthenticateUser is used to authenticate a user against a LDAP/AD.
|
||||
func (service *ldapService) AuthenticateUser(username, password string, settings *portainer.LDAPSettings) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserGroups is used to retrieve user groups from LDAP/AD.
|
||||
func (service *ldapService) GetUserGroups(username string, settings *portainer.LDAPSettings, useAutoAdminSearchSettings bool) ([]string, error) {
|
||||
if useAutoAdminSearchSettings {
|
||||
return []string{"stuff", "operator"}, nil
|
||||
}
|
||||
return []string{"stuff"}, nil
|
||||
}
|
||||
|
||||
// SearchGroups searches for groups with the specified settings
|
||||
func (service *ldapService) SearchAdminGroups(settings *portainer.LDAPSettings) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (service *ldapService) TestConnectivity(settings *portainer.LDAPSettings) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *ldapService) SearchGroups(settings *portainer.LDAPSettings) ([]portainer.LDAPUser, error) {
|
||||
return []portainer.LDAPUser{}, nil
|
||||
}
|
||||
|
||||
func (service *ldapService) SearchUsers(settings *portainer.LDAPSettings) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
@@ -87,7 +88,7 @@ func (*Service) AuthenticateUser(username, password string, settings *portainer.
|
||||
}
|
||||
|
||||
// GetUserGroups is used to retrieve user groups from LDAP/AD.
|
||||
func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings) ([]string, error) {
|
||||
func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings, useAutoAdminSearchSettings bool) ([]string, error) {
|
||||
connection, err := createConnection(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -106,7 +107,78 @@ func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userGroups := getGroupsByUser(userDN, connection, settings.GroupSearchSettings)
|
||||
groupSearchSettings := settings.GroupSearchSettings
|
||||
if useAutoAdminSearchSettings {
|
||||
groupSearchSettings = settings.AdminGroupSearchSettings
|
||||
}
|
||||
|
||||
userGroups := getGroupsByUser(userDN, connection, groupSearchSettings)
|
||||
|
||||
return userGroups, nil
|
||||
}
|
||||
|
||||
// SearchAdminGroups searches for groups with the specified settings
|
||||
func (*Service) SearchAdminGroups(settings *portainer.LDAPSettings) ([]string, error) {
|
||||
userGroups, err := searchUserGroups(settings, true)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed searching user groups")
|
||||
}
|
||||
|
||||
deduplicatedGroups := make(map[string]struct{})
|
||||
for _, gs := range userGroups {
|
||||
for _, group := range gs {
|
||||
deduplicatedGroups[group] = struct{}{}
|
||||
}
|
||||
}
|
||||
groups := make([]string, 0, len(deduplicatedGroups))
|
||||
for group := range deduplicatedGroups {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func searchUserGroups(settings *portainer.LDAPSettings, useAutoAdminSearchSettings bool) (map[string][]string, error) {
|
||||
connection, err := createConnection(settings)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to esteblish an LDAP connection")
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
if !settings.AnonymousMode {
|
||||
if err := connection.Bind(settings.ReaderDN, settings.Password); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to bind an LDAP connection")
|
||||
}
|
||||
}
|
||||
|
||||
groupSearchSettings := settings.GroupSearchSettings
|
||||
if useAutoAdminSearchSettings {
|
||||
groupSearchSettings = settings.AdminGroupSearchSettings
|
||||
}
|
||||
|
||||
userGroups := make(map[string][]string)
|
||||
|
||||
for _, searchSettings := range groupSearchSettings {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
searchSettings.GroupBaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
searchSettings.GroupFilter,
|
||||
[]string{"cn", searchSettings.GroupAttribute},
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err := connection.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to perform a user groups search")
|
||||
}
|
||||
|
||||
for _, entry := range sr.Entries {
|
||||
members := entry.GetAttributeValues(searchSettings.GroupAttribute)
|
||||
for _, username := range members {
|
||||
userGroups[username] = append(userGroups[username], entry.GetAttributeValue("cn"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userGroups, nil
|
||||
}
|
||||
|
||||
@@ -510,7 +510,10 @@ type (
|
||||
SearchSettings []LDAPSearchSettings `json:"SearchSettings"`
|
||||
GroupSearchSettings []LDAPGroupSearchSettings `json:"GroupSearchSettings"`
|
||||
// Automatically provision users and assign them to matching LDAP group names
|
||||
AutoCreateUsers bool `json:"AutoCreateUsers" example:"true"`
|
||||
AutoCreateUsers bool `json:"AutoCreateUsers" example:"true"`
|
||||
AdminAutoPopulate bool `json:"AdminAutoPopulate" example:"true"`
|
||||
AdminGroupSearchSettings []LDAPGroupSearchSettings `json:"AdminGroupSearchSettings"`
|
||||
AdminGroups []string `json:"AdminGroups" example:"['manager','operator']"`
|
||||
}
|
||||
|
||||
// LDAPUser represents a LDAP user
|
||||
@@ -1300,7 +1303,8 @@ type (
|
||||
LDAPService interface {
|
||||
AuthenticateUser(username, password string, settings *LDAPSettings) error
|
||||
TestConnectivity(settings *LDAPSettings) error
|
||||
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
|
||||
GetUserGroups(username string, settings *LDAPSettings, useAutoAdminSearchSettings bool) ([]string, error)
|
||||
SearchAdminGroups(settings *LDAPSettings) ([]string, error)
|
||||
SearchGroups(settings *LDAPSettings) ([]LDAPUser, error)
|
||||
SearchUsers(settings *LDAPSettings) ([]string, error)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ angular
|
||||
.constant('API_ENDPOINT_REGISTRIES', 'api/registries')
|
||||
.constant('API_ENDPOINT_RESOURCE_CONTROLS', 'api/resource_controls')
|
||||
.constant('API_ENDPOINT_SETTINGS', 'api/settings')
|
||||
.constant('API_ENDPOINT_LDAP', 'api/ldap')
|
||||
.constant('API_ENDPOINT_STACKS', 'api/stacks')
|
||||
.constant('API_ENDPOINT_STATUS', 'api/status')
|
||||
.constant('API_ENDPOINT_SUPPORT', 'api/support')
|
||||
|
||||
@@ -21,436 +21,438 @@ async function initAuthentication(authManager, Authentication, $rootScope, $stat
|
||||
return await Authentication.init();
|
||||
}
|
||||
|
||||
angular.module('portainer.app', ['portainer.oauth', 'portainer.rbac', componentsModule, settingsModule, featureFlagModule, userActivityModule, 'portainer.shared.datatable']).config([
|
||||
'$stateRegistryProvider',
|
||||
function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
angular
|
||||
.module('portainer.app', ['portainer.oauth', 'portainer.rbac', componentsModule, settingsModule, featureFlagModule, userActivityModule, 'portainer.shared.datatable'])
|
||||
.config([
|
||||
'$stateRegistryProvider',
|
||||
function ($stateRegistryProvider) {
|
||||
'use strict';
|
||||
|
||||
var root = {
|
||||
name: 'root',
|
||||
abstract: true,
|
||||
onEnter: /* @ngInject */ function onEnter($async, StateManager, Authentication, Notifications, authManager, $rootScope, $state) {
|
||||
return $async(async () => {
|
||||
const appState = StateManager.getState();
|
||||
if (!appState.loading) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const loggedIn = await initAuthentication(authManager, Authentication, $rootScope, $state);
|
||||
await StateManager.initialize();
|
||||
if (!loggedIn && isTransitionRequiresAuthentication($state.transition)) {
|
||||
$state.go('portainer.logout');
|
||||
return Promise.reject('Unauthenticated');
|
||||
}
|
||||
} catch (err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
views: {
|
||||
'sidebar@': {
|
||||
templateUrl: './views/sidebar/sidebar.html',
|
||||
controller: 'SidebarController',
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
featuresServiceInitialized: /* @ngInject */ function featuresServiceInitialized($async, featureService, Notifications) {
|
||||
var root = {
|
||||
name: 'root',
|
||||
abstract: true,
|
||||
onEnter: /* @ngInject */ function onEnter($async, StateManager, Authentication, Notifications, authManager, $rootScope, $state) {
|
||||
return $async(async () => {
|
||||
try {
|
||||
await featureService.init();
|
||||
} catch (e) {
|
||||
Notifications.error('Failed initializing features service', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var endpointRoot = {
|
||||
name: 'endpoint',
|
||||
url: '/:endpointId',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
resolve: {
|
||||
endpoint: /* @ngInject */ function endpoint($async, $state, $transition$, EndpointService, Notifications) {
|
||||
return $async(async () => {
|
||||
try {
|
||||
const endpointId = +$transition$.params().endpointId;
|
||||
|
||||
const endpoint = await EndpointService.endpoint(endpointId);
|
||||
if ((endpoint.Type === 4 || endpoint.Type === 7) && !endpoint.EdgeID) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
|
||||
return;
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
} catch (e) {
|
||||
Notifications.error('Failed loading environment', e);
|
||||
$state.go('portainer.home', {}, { reload: true });
|
||||
const appState = StateManager.getState();
|
||||
if (!appState.loading) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const loggedIn = await initAuthentication(authManager, Authentication, $rootScope, $state);
|
||||
await StateManager.initialize();
|
||||
if (!loggedIn && isTransitionRequiresAuthentication($state.transition)) {
|
||||
$state.go('portainer.logout');
|
||||
return Promise.reject('Unauthenticated');
|
||||
}
|
||||
} catch (err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var portainer = {
|
||||
name: 'portainer',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
};
|
||||
|
||||
var account = {
|
||||
name: 'portainer.account',
|
||||
url: '/account',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/account/account.html',
|
||||
controller: 'AccountController',
|
||||
views: {
|
||||
'sidebar@': {
|
||||
templateUrl: './views/sidebar/sidebar.html',
|
||||
controller: 'SidebarController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var authentication = {
|
||||
name: 'portainer.auth',
|
||||
url: '/auth',
|
||||
params: {
|
||||
reload: false,
|
||||
},
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/auth/auth.html',
|
||||
controller: 'AuthenticationController',
|
||||
controllerAs: 'ctrl',
|
||||
resolve: {
|
||||
featuresServiceInitialized: /* @ngInject */ function featuresServiceInitialized($async, featureService, Notifications) {
|
||||
return $async(async () => {
|
||||
try {
|
||||
await featureService.init();
|
||||
} catch (e) {
|
||||
Notifications.error('Failed initializing features service', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
'sidebar@': {},
|
||||
},
|
||||
};
|
||||
const logout = {
|
||||
name: 'portainer.logout',
|
||||
url: '/logout',
|
||||
params: {
|
||||
error: '',
|
||||
performApiLogout: false,
|
||||
},
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/logout/logout.html',
|
||||
controller: 'LogoutController',
|
||||
controllerAs: 'ctrl',
|
||||
};
|
||||
|
||||
var endpointRoot = {
|
||||
name: 'endpoint',
|
||||
url: '/:endpointId',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
resolve: {
|
||||
endpoint: /* @ngInject */ function endpoint($async, $state, $transition$, EndpointService, Notifications) {
|
||||
return $async(async () => {
|
||||
try {
|
||||
const endpointId = +$transition$.params().endpointId;
|
||||
|
||||
const endpoint = await EndpointService.endpoint(endpointId);
|
||||
if ((endpoint.Type === 4 || endpoint.Type === 7) && !endpoint.EdgeID) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
|
||||
return;
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
} catch (e) {
|
||||
Notifications.error('Failed loading environment', e);
|
||||
$state.go('portainer.home', {}, { reload: true });
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
'sidebar@': {},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var endpoints = {
|
||||
name: 'portainer.endpoints',
|
||||
url: '/endpoints',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/endpoints.html',
|
||||
controller: 'EndpointsController',
|
||||
var portainer = {
|
||||
name: 'portainer',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
};
|
||||
|
||||
var account = {
|
||||
name: 'portainer.account',
|
||||
url: '/account',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/account/account.html',
|
||||
controller: 'AccountController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var endpoint = {
|
||||
name: 'portainer.endpoints.endpoint',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/edit/endpoint.html',
|
||||
controller: 'EndpointController',
|
||||
var authentication = {
|
||||
name: 'portainer.auth',
|
||||
url: '/auth',
|
||||
params: {
|
||||
reload: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const endpointKubernetesConfiguration = {
|
||||
name: 'portainer.endpoints.endpoint.kubernetesConfig',
|
||||
url: '/configure',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: '../kubernetes/views/configure/configure.html',
|
||||
controller: 'KubernetesConfigureController',
|
||||
controllerAs: 'ctrl',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/auth/auth.html',
|
||||
controller: 'AuthenticationController',
|
||||
controllerAs: 'ctrl',
|
||||
},
|
||||
'sidebar@': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var endpointCreation = {
|
||||
name: 'portainer.endpoints.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/create/createendpoint.html',
|
||||
controller: 'CreateEndpointController',
|
||||
};
|
||||
const logout = {
|
||||
name: 'portainer.logout',
|
||||
url: '/logout',
|
||||
params: {
|
||||
error: '',
|
||||
performApiLogout: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var endpointAccess = {
|
||||
name: 'portainer.endpoints.endpoint.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/access/endpointAccess.html',
|
||||
controller: 'EndpointAccessController',
|
||||
controllerAs: 'ctrl',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/logout/logout.html',
|
||||
controller: 'LogoutController',
|
||||
controllerAs: 'ctrl',
|
||||
},
|
||||
'sidebar@': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var groups = {
|
||||
name: 'portainer.groups',
|
||||
url: '/groups',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/groups.html',
|
||||
controller: 'GroupsController',
|
||||
var endpoints = {
|
||||
name: 'portainer.endpoints',
|
||||
url: '/endpoints',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/endpoints.html',
|
||||
controller: 'EndpointsController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var group = {
|
||||
name: 'portainer.groups.group',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/edit/group.html',
|
||||
controller: 'GroupController',
|
||||
var endpoint = {
|
||||
name: 'portainer.endpoints.endpoint',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/edit/endpoint.html',
|
||||
controller: 'EndpointController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var groupCreation = {
|
||||
name: 'portainer.groups.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/create/creategroup.html',
|
||||
controller: 'CreateGroupController',
|
||||
const endpointKubernetesConfiguration = {
|
||||
name: 'portainer.endpoints.endpoint.kubernetesConfig',
|
||||
url: '/configure',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: '../kubernetes/views/configure/configure.html',
|
||||
controller: 'KubernetesConfigureController',
|
||||
controllerAs: 'ctrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var groupAccess = {
|
||||
name: 'portainer.groups.group.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/access/groupAccess.html',
|
||||
controller: 'GroupAccessController',
|
||||
var endpointCreation = {
|
||||
name: 'portainer.endpoints.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/create/createendpoint.html',
|
||||
controller: 'CreateEndpointController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var home = {
|
||||
name: 'portainer.home',
|
||||
url: '/home',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/home/home.html',
|
||||
controller: 'HomeController',
|
||||
var endpointAccess = {
|
||||
name: 'portainer.endpoints.endpoint.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/endpoints/access/endpointAccess.html',
|
||||
controller: 'EndpointAccessController',
|
||||
controllerAs: 'ctrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var init = {
|
||||
name: 'portainer.init',
|
||||
abstract: true,
|
||||
url: '/init',
|
||||
views: {
|
||||
'sidebar@': {},
|
||||
},
|
||||
};
|
||||
|
||||
const wizard = {
|
||||
name: 'portainer.wizard',
|
||||
url: '/wizard',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'wizardView',
|
||||
var groups = {
|
||||
name: 'portainer.groups',
|
||||
url: '/groups',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/groups.html',
|
||||
controller: 'GroupsController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const wizardEndpoints = {
|
||||
name: 'portainer.wizard.endpoints',
|
||||
url: '/endpoints',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'wizardEndpoints',
|
||||
var group = {
|
||||
name: 'portainer.groups.group',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/edit/group.html',
|
||||
controller: 'GroupController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var initEndpoint = {
|
||||
name: 'portainer.init.endpoint',
|
||||
url: '/endpoint',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/init/endpoint/initEndpoint.html',
|
||||
controller: 'InitEndpointController',
|
||||
controllerAs: 'ctrl',
|
||||
var groupCreation = {
|
||||
name: 'portainer.groups.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/create/creategroup.html',
|
||||
controller: 'CreateGroupController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var initAdmin = {
|
||||
name: 'portainer.init.admin',
|
||||
url: '/admin',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/init/admin/initAdmin.html',
|
||||
controller: 'InitAdminController',
|
||||
var groupAccess = {
|
||||
name: 'portainer.groups.group.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/access/groupAccess.html',
|
||||
controller: 'GroupAccessController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var registries = {
|
||||
name: 'portainer.registries',
|
||||
url: '/registries',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/registries.html',
|
||||
controller: 'RegistriesController',
|
||||
var home = {
|
||||
name: 'portainer.home',
|
||||
url: '/home',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/home/home.html',
|
||||
controller: 'HomeController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var registry = {
|
||||
name: 'portainer.registries.registry',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/edit/registry.html',
|
||||
controller: 'RegistryController',
|
||||
var init = {
|
||||
name: 'portainer.init',
|
||||
abstract: true,
|
||||
url: '/init',
|
||||
views: {
|
||||
'sidebar@': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const registryCreation = {
|
||||
name: 'portainer.registries.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createRegistry',
|
||||
const wizard = {
|
||||
name: 'portainer.wizard',
|
||||
url: '/wizard',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'wizardView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var settings = {
|
||||
name: 'portainer.settings',
|
||||
url: '/settings',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/settings/settings.html',
|
||||
controller: 'SettingsController',
|
||||
const wizardEndpoints = {
|
||||
name: 'portainer.wizard.endpoints',
|
||||
url: '/endpoints',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'wizardEndpoints',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var settingsAuthentication = {
|
||||
name: 'portainer.settings.authentication',
|
||||
url: '/auth',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/settings/authentication/settingsAuthentication.html',
|
||||
controller: 'SettingsAuthenticationController',
|
||||
var initEndpoint = {
|
||||
name: 'portainer.init.endpoint',
|
||||
url: '/endpoint',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/init/endpoint/initEndpoint.html',
|
||||
controller: 'InitEndpointController',
|
||||
controllerAs: 'ctrl',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var tags = {
|
||||
name: 'portainer.tags',
|
||||
url: '/tags',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/tags/tags.html',
|
||||
controller: 'TagsController',
|
||||
var initAdmin = {
|
||||
name: 'portainer.init.admin',
|
||||
url: '/admin',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/init/admin/initAdmin.html',
|
||||
controller: 'InitAdminController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var users = {
|
||||
name: 'portainer.users',
|
||||
url: '/users',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/users/users.html',
|
||||
controller: 'UsersController',
|
||||
var registries = {
|
||||
name: 'portainer.registries',
|
||||
url: '/registries',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/registries.html',
|
||||
controller: 'RegistriesController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var user = {
|
||||
name: 'portainer.users.user',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/users/edit/user.html',
|
||||
controller: 'UserController',
|
||||
var registry = {
|
||||
name: 'portainer.registries.registry',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/registries/edit/registry.html',
|
||||
controller: 'RegistryController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var teams = {
|
||||
name: 'portainer.teams',
|
||||
url: '/teams',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/teams/teams.html',
|
||||
controller: 'TeamsController',
|
||||
const registryCreation = {
|
||||
name: 'portainer.registries.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createRegistry',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var team = {
|
||||
name: 'portainer.teams.team',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/teams/edit/team.html',
|
||||
controller: 'TeamController',
|
||||
var settings = {
|
||||
name: 'portainer.settings',
|
||||
url: '/settings',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/settings/settings.html',
|
||||
controller: 'SettingsController',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(root);
|
||||
$stateRegistryProvider.register(endpointRoot);
|
||||
$stateRegistryProvider.register(portainer);
|
||||
$stateRegistryProvider.register(account);
|
||||
$stateRegistryProvider.register(authentication);
|
||||
$stateRegistryProvider.register(logout);
|
||||
$stateRegistryProvider.register(endpoints);
|
||||
$stateRegistryProvider.register(endpoint);
|
||||
$stateRegistryProvider.register(endpointAccess);
|
||||
$stateRegistryProvider.register(endpointCreation);
|
||||
$stateRegistryProvider.register(endpointKubernetesConfiguration);
|
||||
$stateRegistryProvider.register(groups);
|
||||
$stateRegistryProvider.register(group);
|
||||
$stateRegistryProvider.register(groupAccess);
|
||||
$stateRegistryProvider.register(groupCreation);
|
||||
$stateRegistryProvider.register(home);
|
||||
$stateRegistryProvider.register(init);
|
||||
$stateRegistryProvider.register(wizard);
|
||||
$stateRegistryProvider.register(wizardEndpoints);
|
||||
$stateRegistryProvider.register(initEndpoint);
|
||||
$stateRegistryProvider.register(initAdmin);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registry);
|
||||
$stateRegistryProvider.register(registryCreation);
|
||||
$stateRegistryProvider.register(settings);
|
||||
$stateRegistryProvider.register(settingsAuthentication);
|
||||
$stateRegistryProvider.register(tags);
|
||||
$stateRegistryProvider.register(users);
|
||||
$stateRegistryProvider.register(user);
|
||||
$stateRegistryProvider.register(teams);
|
||||
$stateRegistryProvider.register(team);
|
||||
},
|
||||
]);
|
||||
var settingsAuthentication = {
|
||||
name: 'portainer.settings.authentication',
|
||||
url: '/auth',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/settings/authentication/settingsAuthentication.html',
|
||||
controller: 'SettingsAuthenticationController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var tags = {
|
||||
name: 'portainer.tags',
|
||||
url: '/tags',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/tags/tags.html',
|
||||
controller: 'TagsController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var users = {
|
||||
name: 'portainer.users',
|
||||
url: '/users',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/users/users.html',
|
||||
controller: 'UsersController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var user = {
|
||||
name: 'portainer.users.user',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/users/edit/user.html',
|
||||
controller: 'UserController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var teams = {
|
||||
name: 'portainer.teams',
|
||||
url: '/teams',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/teams/teams.html',
|
||||
controller: 'TeamsController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var team = {
|
||||
name: 'portainer.teams.team',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/teams/edit/team.html',
|
||||
controller: 'TeamController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(root);
|
||||
$stateRegistryProvider.register(endpointRoot);
|
||||
$stateRegistryProvider.register(portainer);
|
||||
$stateRegistryProvider.register(account);
|
||||
$stateRegistryProvider.register(authentication);
|
||||
$stateRegistryProvider.register(logout);
|
||||
$stateRegistryProvider.register(endpoints);
|
||||
$stateRegistryProvider.register(endpoint);
|
||||
$stateRegistryProvider.register(endpointAccess);
|
||||
$stateRegistryProvider.register(endpointCreation);
|
||||
$stateRegistryProvider.register(endpointKubernetesConfiguration);
|
||||
$stateRegistryProvider.register(groups);
|
||||
$stateRegistryProvider.register(group);
|
||||
$stateRegistryProvider.register(groupAccess);
|
||||
$stateRegistryProvider.register(groupCreation);
|
||||
$stateRegistryProvider.register(home);
|
||||
$stateRegistryProvider.register(init);
|
||||
$stateRegistryProvider.register(wizard);
|
||||
$stateRegistryProvider.register(wizardEndpoints);
|
||||
$stateRegistryProvider.register(initEndpoint);
|
||||
$stateRegistryProvider.register(initAdmin);
|
||||
$stateRegistryProvider.register(registries);
|
||||
$stateRegistryProvider.register(registry);
|
||||
$stateRegistryProvider.register(registryCreation);
|
||||
$stateRegistryProvider.register(settings);
|
||||
$stateRegistryProvider.register(settingsAuthentication);
|
||||
$stateRegistryProvider.register(tags);
|
||||
$stateRegistryProvider.register(users);
|
||||
$stateRegistryProvider.register(user);
|
||||
$stateRegistryProvider.register(teams);
|
||||
$stateRegistryProvider.register(team);
|
||||
},
|
||||
]);
|
||||
|
||||
function isTransitionRequiresAuthentication(transition) {
|
||||
const UNAUTHENTICATED_ROUTES = ['portainer.logout', 'portainer.auth'];
|
||||
|
||||
14
app/portainer/rest/ldap.js
Normal file
14
app/portainer/rest/ldap.js
Normal file
@@ -0,0 +1,14 @@
|
||||
angular.module('portainer.app').factory('LDAP', [
|
||||
'$resource',
|
||||
'API_ENDPOINT_LDAP',
|
||||
function SettingsFactory($resource, API_ENDPOINT_LDAP) {
|
||||
'use strict';
|
||||
return $resource(
|
||||
API_ENDPOINT_LDAP + '/:action',
|
||||
{},
|
||||
{
|
||||
adminGroups: { method: 'POST', isArray: true, params: { action: 'admin-groups' } },
|
||||
}
|
||||
);
|
||||
},
|
||||
]);
|
||||
28
app/portainer/services/api/ldapService.js
Normal file
28
app/portainer/services/api/ldapService.js
Normal file
@@ -0,0 +1,28 @@
|
||||
angular.module('portainer.app').factory('LDAPService', [
|
||||
'$q',
|
||||
'LDAP',
|
||||
function LdapServiceFactory($q, LDAP) {
|
||||
'use strict';
|
||||
var service = {};
|
||||
|
||||
service.adminGroups = function (ldapSettings) {
|
||||
var deferred = $q.defer();
|
||||
LDAP.adminGroups({ ldapSettings })
|
||||
.$promise.then(function success(data) {
|
||||
const userGroups = data
|
||||
.sort((a, b) => {
|
||||
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
|
||||
})
|
||||
.map((name) => ({ name, selected: ldapSettings.AdminGroups && ldapSettings.AdminGroups.includes(name) ? true : false }));
|
||||
deferred.resolve(userGroups);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
deferred.reject({ msg: 'Unable to retrieve admin grous', err: err });
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
@@ -23,6 +23,14 @@ export function buildLdapSettingsModel() {
|
||||
GroupAttribute: '',
|
||||
},
|
||||
],
|
||||
AdminGroupSearchSettings: [
|
||||
{
|
||||
GroupBaseDN: '',
|
||||
GroupFilter: '',
|
||||
GroupAttribute: '',
|
||||
},
|
||||
],
|
||||
AdminAutoPopulate: false,
|
||||
AutoCreateUsers: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ class AuthenticationController {
|
||||
this.generateOAuthLoginURI();
|
||||
return;
|
||||
}
|
||||
if(!this.logo){
|
||||
if (!this.logo) {
|
||||
await this.StateManager.initialize();
|
||||
this.logo = this.StateManager.getState().application.logo;
|
||||
}
|
||||
|
||||
@@ -168,14 +168,14 @@
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
ngf-select
|
||||
accept=".gz,.encrypted"
|
||||
ngf-accept="'application/x-tar,application/x-gzip'"
|
||||
ng-model="formValues.BackupFile"
|
||||
auto-focus
|
||||
>Select file</button
|
||||
>
|
||||
class="btn btn-sm btn-primary"
|
||||
ngf-select
|
||||
accept=".gz,.encrypted"
|
||||
ngf-accept="'application/x-tar,application/x-gzip'"
|
||||
ng-model="formValues.BackupFile"
|
||||
auto-focus
|
||||
>Select file</button
|
||||
>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ formValues.BackupFile.name }}
|
||||
<i class="fa fa-times red-icon" ng-if="!formValues.BackupFile" aria-hidden="true"></i>
|
||||
|
||||
@@ -7,6 +7,8 @@ import { buildLdapSettingsModel, buildAdSettingsModel } from '@/portainer/settin
|
||||
angular.module('portainer.app').controller('SettingsAuthenticationController', SettingsAuthenticationController);
|
||||
|
||||
function SettingsAuthenticationController($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService, LDAPService) {
|
||||
const DEFAULT_GROUP_FILTER = '(objectClass=groupOfNames)';
|
||||
|
||||
$scope.state = {
|
||||
uploadInProgress: false,
|
||||
actionInProgress: false,
|
||||
@@ -32,6 +34,7 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
||||
{ key: '6 months', value: `${24 * 30 * 6}h` },
|
||||
{ key: '1 year', value: `${24 * 30 * 12}h` },
|
||||
],
|
||||
enableAssignAdminGroup: false,
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
@@ -42,6 +45,19 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
||||
adSettings: buildAdSettingsModel(),
|
||||
ldapSettings: buildLdapSettingsModel(),
|
||||
},
|
||||
selectedAdminGroups: [],
|
||||
};
|
||||
|
||||
$scope.groups = null;
|
||||
|
||||
$scope.searchAdminGroups = async function () {
|
||||
const settings = {
|
||||
...$scope.settings.LDAPSettings,
|
||||
AdminGroupSearchSettings: $scope.settings.LDAPSettings.AdminGroupSearchSettings.map((search) => ({ ...search, GroupFilter: search.GroupFilter || DEFAULT_GROUP_FILTER })),
|
||||
};
|
||||
|
||||
$scope.groups = await LDAPService.adminGroups(settings);
|
||||
$scope.state.enableAssignAdminGroup = $scope.groups && $scope.groups.length > 0;
|
||||
};
|
||||
|
||||
$scope.authOptions = [
|
||||
@@ -120,6 +136,16 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
||||
$scope.saveSettings = function () {
|
||||
const settings = angular.copy($scope.settings);
|
||||
|
||||
if ($scope.formValues.LDAPSettings.AdminAutoPopulate && $scope.formValues.selectedAdminGroups.length > 0) {
|
||||
settings.LDAPSettings.AdminGroups = $scope.formValues.selectedAdminGroups.map((team) => team.name);
|
||||
} else {
|
||||
settings.LDAPSettings.AdminGroups = [];
|
||||
}
|
||||
|
||||
if ($scope.formValues.selectedAdminGroups && $scope.formValues.selectedAdminGroups.length === 0) {
|
||||
settings.LDAPSettings.AdminAutoPopulate = false;
|
||||
}
|
||||
|
||||
const { settings: ldapSettings, uploadRequired, tlscaFile } = prepareLDAPSettings();
|
||||
settings.LDAPSettings = ldapSettings;
|
||||
$scope.state.uploadInProgress = uploadRequired;
|
||||
@@ -201,6 +227,21 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
||||
return true;
|
||||
}
|
||||
|
||||
async function prelodaAdminGroup() {
|
||||
if ($scope.settings.LDAPSettings.AdminAutoPopulate && $scope.settings.LDAPSettings.AdminGroups && $scope.settings.LDAPSettings.AdminGroups.length > 0) {
|
||||
const settings = {
|
||||
...$scope.settings.LDAPSettings,
|
||||
AdminGroupSearchSettings: $scope.settings.LDAPSettings.AdminGroupSearchSettings.map((search) => ({
|
||||
...search,
|
||||
GroupFilter: search.GroupFilter || '(objectClass=groupOfNames)',
|
||||
})),
|
||||
};
|
||||
|
||||
$scope.groups = await LDAPService.adminGroups(settings);
|
||||
$scope.state.enableAssignAdminGroup = $scope.groups && $scope.groups.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$q.all({
|
||||
settings: SettingsService.settings(),
|
||||
@@ -236,6 +277,8 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
||||
if (!settings.LDAPSettings.ServerType) {
|
||||
settings.LDAPSettings.ServerType = 0;
|
||||
}
|
||||
|
||||
prelodaAdminGroup();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
||||
Reference in New Issue
Block a user