Compare commits
23 Commits
2.9.1
...
fix/EE-882
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd0f6b8f47 | ||
|
|
c28f315de1 | ||
|
|
f67230db3f | ||
|
|
5581613d38 | ||
|
|
ad1756f08e | ||
|
|
e825fcb608 | ||
|
|
5138920d2b | ||
|
|
0b8b3fc73f | ||
|
|
8fc18b09ce | ||
|
|
24a40dc787 | ||
|
|
bfcaf3c02a | ||
|
|
14cc745128 | ||
|
|
318d8d0006 | ||
|
|
6b68e294c5 | ||
|
|
82f6a8a46f | ||
|
|
ab9e8b9ea0 | ||
|
|
28c28182ad | ||
|
|
bf491cab67 | ||
|
|
c14084d5b0 | ||
|
|
204fad6535 | ||
|
|
ea12b87ccb | ||
|
|
5bd5b111f7 | ||
|
|
c17e9fd028 |
@@ -93,7 +93,7 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("tcp://127.0.0.1:%d", proxy.Port), proxy, nil
|
||||
return fmt.Sprintf("http://127.0.0.1:%d", proxy.Port), proxy, nil
|
||||
}
|
||||
|
||||
func createEnvFile(stack *portainer.Stack) (string, error) {
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/portainer/portainer/api/http/handler/file"
|
||||
"github.com/portainer/portainer/api/http/handler/helm"
|
||||
"github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
@@ -54,7 +53,6 @@ type Handler struct {
|
||||
HelmTemplatesHandler *helm.Handler
|
||||
KubernetesHandler *kubernetes.Handler
|
||||
FileHandler *file.Handler
|
||||
LDAPHandler *ldap.Handler
|
||||
MOTDHandler *motd.Handler
|
||||
RegistryHandler *registries.Handler
|
||||
ResourceControlHandler *resourcecontrols.Handler
|
||||
@@ -191,8 +189,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
case strings.HasPrefix(r.URL.Path, "/api/ldap"):
|
||||
http.StripPrefix("/api", h.LDAPHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/motd"):
|
||||
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/registries"):
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle LDAP search Operations
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
DataStore portainer.DataStore
|
||||
FileService portainer.FileService
|
||||
LDAPService portainer.LDAPService
|
||||
}
|
||||
|
||||
// NewHandler returns a new Handler
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
|
||||
h.Handle("/ldap/check",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.ldapCheck))).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) prefillSettings(ldapSettings *portainer.LDAPSettings) error {
|
||||
if !ldapSettings.AnonymousMode && ldapSettings.Password == "" {
|
||||
settings, err := handler.DataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ldapSettings.Password = settings.LDAPSettings.Password
|
||||
}
|
||||
|
||||
if (ldapSettings.TLSConfig.TLS || ldapSettings.StartTLS) && !ldapSettings.TLSConfig.TLSSkipVerify {
|
||||
caCertPath, err := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ldapSettings.TLSConfig.TLSCACertPath = caCertPath
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
@@ -35,6 +35,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/settings/public",
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.settingsPublic))).Methods(http.MethodGet)
|
||||
h.Handle("/settings/authentication/checkLDAP",
|
||||
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package ldap
|
||||
package settings
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -7,43 +7,42 @@ import (
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
)
|
||||
|
||||
type checkPayload struct {
|
||||
type settingsLDAPCheckPayload struct {
|
||||
LDAPSettings portainer.LDAPSettings
|
||||
}
|
||||
|
||||
func (payload *checkPayload) Validate(r *http.Request) error {
|
||||
func (payload *settingsLDAPCheckPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @id LDAPCheck
|
||||
// @id SettingsLDAPCheck
|
||||
// @summary Test LDAP connectivity
|
||||
// @description Test LDAP connectivity using LDAP details
|
||||
// @description **Access policy**: administrator
|
||||
// @tags ldap
|
||||
// @tags settings
|
||||
// @security jwt
|
||||
// @accept json
|
||||
// @param body body checkPayload true "details"
|
||||
// @param body body settingsLDAPCheckPayload true "details"
|
||||
// @success 204 "Success"
|
||||
// @failure 400 "Invalid request"
|
||||
// @failure 500 "Server error"
|
||||
// @router /ldap/check [post]
|
||||
func (handler *Handler) ldapCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload checkPayload
|
||||
// @router /settings/ldap/check [put]
|
||||
func (handler *Handler) settingsLDAPCheck(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
var payload settingsLDAPCheckPayload
|
||||
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}
|
||||
if (payload.LDAPSettings.TLSConfig.TLS || payload.LDAPSettings.StartTLS) && !payload.LDAPSettings.TLSConfig.TLSSkipVerify {
|
||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(filesystem.LDAPStorePath, portainer.TLSFileCA)
|
||||
payload.LDAPSettings.TLSConfig.TLSCACertPath = caCertPath
|
||||
}
|
||||
|
||||
err = handler.LDAPService.TestConnectivity(settings)
|
||||
err = handler.LDAPService.TestConnectivity(&payload.LDAPSettings)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to connect to LDAP server", err}
|
||||
}
|
||||
@@ -10,21 +10,13 @@ import (
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// @summary Execute a websocket on kubectl shell pod
|
||||
// @description The request will be upgraded to the websocket protocol. The request will proxy input from the client to the pod via long-lived websocket connection.
|
||||
// @description **Access policy**: authenticated
|
||||
// @security jwt
|
||||
// @tags websocket
|
||||
// @accept json
|
||||
// @produce json
|
||||
// @param endpointId query int true "environment(endpoint) ID of the environment(endpoint) where the resource is located"
|
||||
// @param token query string true "JWT token used for authentication against this environment(endpoint)"
|
||||
// @success 200
|
||||
// @failure 400
|
||||
// @failure 403
|
||||
// @failure 404
|
||||
// @failure 500
|
||||
// @router /websocket/kubernetes-shell [get]
|
||||
// websocketShellPodExec handles GET requests on /websocket/pod?token=<token>&endpointId=<endpointID>
|
||||
// The request will be upgraded to the websocket protocol.
|
||||
// Authentication and access is controlled via the mandatory token query parameter.
|
||||
// The request will proxy input from the client to the pod via long-lived websocket connection.
|
||||
// The following query parameters are mandatory:
|
||||
// * token: JWT token used for authentication against this environment(endpoint)
|
||||
// * endpointId: environment(endpoint) ID of the environment(endpoint) where the resource is located
|
||||
func (handler *Handler) websocketShellPodExec(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/portainer/portainer/api/http/handler/file"
|
||||
"github.com/portainer/portainer/api/http/handler/helm"
|
||||
kubehandler "github.com/portainer/portainer/api/http/handler/kubernetes"
|
||||
"github.com/portainer/portainer/api/http/handler/ldap"
|
||||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
@@ -176,11 +175,6 @@ func (server *Server) Start() error {
|
||||
|
||||
var helmTemplatesHandler = helm.NewTemplateHandler(requestBouncer, server.HelmPackageManager)
|
||||
|
||||
var ldapHandler = ldap.NewHandler(requestBouncer)
|
||||
ldapHandler.DataStore = server.DataStore
|
||||
ldapHandler.FileService = server.FileService
|
||||
ldapHandler.LDAPService = server.LDAPService
|
||||
|
||||
var motdHandler = motd.NewHandler(requestBouncer)
|
||||
|
||||
var registryHandler = registries.NewHandler(requestBouncer)
|
||||
@@ -261,7 +255,6 @@ func (server *Server) Start() error {
|
||||
EndpointEdgeHandler: endpointEdgeHandler,
|
||||
EndpointProxyHandler: endpointProxyHandler,
|
||||
FileHandler: fileHandler,
|
||||
LDAPHandler: ldapHandler,
|
||||
HelmTemplatesHandler: helmTemplatesHandler,
|
||||
KubernetesHandler: kubernetesHandler,
|
||||
MOTDHandler: motdHandler,
|
||||
|
||||
212
api/ldap/ldap.go
212
api/ldap/ldap.go
@@ -1,11 +1,11 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
@@ -20,28 +20,55 @@ var (
|
||||
// Service represents a service used to authenticate users against a LDAP/AD.
|
||||
type Service struct{}
|
||||
|
||||
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
|
||||
conn, err := createConnectionForURL(settings.URL, settings)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed creating LDAP connection")
|
||||
func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) {
|
||||
var userDN string
|
||||
found := false
|
||||
usernameEscaped := ldap.EscapeFilter(username)
|
||||
|
||||
for _, searchSettings := range settings {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
searchSettings.BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, usernameEscaped),
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Deliberately skip errors on the search request so that we can jump to other search settings
|
||||
// if any issue arise with the current one.
|
||||
sr, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sr.Entries) == 1 {
|
||||
found = true
|
||||
userDN = sr.Entries[0].DN
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
if !found {
|
||||
return "", errUserNotFound
|
||||
}
|
||||
|
||||
return userDN, nil
|
||||
}
|
||||
|
||||
func createConnectionForURL(url string, settings *portainer.LDAPSettings) (*ldap.Conn, error) {
|
||||
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
|
||||
|
||||
if settings.TLSConfig.TLS || settings.StartTLS {
|
||||
config, err := crypto.CreateTLSConfigurationFromDisk(settings.TLSConfig.TLSCACertPath, settings.TLSConfig.TLSCertPath, settings.TLSConfig.TLSKeyPath, settings.TLSConfig.TLSSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.ServerName = strings.Split(url, ":")[0]
|
||||
config.ServerName = strings.Split(settings.URL, ":")[0]
|
||||
|
||||
if settings.TLSConfig.TLS {
|
||||
return ldap.DialTLS("tcp", url, config)
|
||||
return ldap.DialTLS("tcp", settings.URL, config)
|
||||
}
|
||||
|
||||
conn, err := ldap.Dial("tcp", url)
|
||||
conn, err := ldap.Dial("tcp", settings.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -54,7 +81,7 @@ func createConnectionForURL(url string, settings *portainer.LDAPSettings) (*ldap
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
return ldap.Dial("tcp", url)
|
||||
return ldap.Dial("tcp", settings.URL)
|
||||
}
|
||||
|
||||
// AuthenticateUser is used to authenticate a user against a LDAP/AD.
|
||||
@@ -106,157 +133,13 @@ func (*Service) GetUserGroups(username string, settings *portainer.LDAPSettings)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userGroups := getGroupsByUser(userDN, connection, settings.GroupSearchSettings)
|
||||
userGroups := getGroups(userDN, connection, settings.GroupSearchSettings)
|
||||
|
||||
return userGroups, nil
|
||||
}
|
||||
|
||||
// SearchUsers searches for users with the specified settings
|
||||
func (*Service) SearchUsers(settings *portainer.LDAPSettings) ([]string, error) {
|
||||
connection, err := createConnection(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
if !settings.AnonymousMode {
|
||||
err = connection.Bind(settings.ReaderDN, settings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
users := map[string]bool{}
|
||||
|
||||
for _, searchSettings := range settings.SearchSettings {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
searchSettings.BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
searchSettings.Filter,
|
||||
[]string{"dn", searchSettings.UserNameAttribute},
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err := connection.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range sr.Entries {
|
||||
username := user.GetAttributeValue(searchSettings.UserNameAttribute)
|
||||
if username != "" {
|
||||
users[username] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usersList := []string{}
|
||||
for user := range users {
|
||||
usersList = append(usersList, user)
|
||||
}
|
||||
|
||||
return usersList, nil
|
||||
}
|
||||
|
||||
// SearchGroups searches for groups with the specified settings
|
||||
func (*Service) SearchGroups(settings *portainer.LDAPSettings) ([]portainer.LDAPUser, error) {
|
||||
type groupSet map[string]bool
|
||||
|
||||
connection, err := createConnection(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
if !settings.AnonymousMode {
|
||||
err = connection.Bind(settings.ReaderDN, settings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
userGroups := map[string]groupSet{}
|
||||
|
||||
for _, searchSettings := range settings.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, err
|
||||
}
|
||||
|
||||
for _, entry := range sr.Entries {
|
||||
members := entry.GetAttributeValues(searchSettings.GroupAttribute)
|
||||
for _, username := range members {
|
||||
_, ok := userGroups[username]
|
||||
if !ok {
|
||||
userGroups[username] = groupSet{}
|
||||
}
|
||||
userGroups[username][entry.GetAttributeValue("cn")] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
users := []portainer.LDAPUser{}
|
||||
|
||||
for username, groups := range userGroups {
|
||||
groupList := []string{}
|
||||
for group := range groups {
|
||||
groupList = append(groupList, group)
|
||||
}
|
||||
user := portainer.LDAPUser{
|
||||
Name: username,
|
||||
Groups: groupList,
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearchSettings) (string, error) {
|
||||
var userDN string
|
||||
found := false
|
||||
usernameEscaped := ldap.EscapeFilter(username)
|
||||
|
||||
for _, searchSettings := range settings {
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
searchSettings.BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf("(&%s(%s=%s))", searchSettings.Filter, searchSettings.UserNameAttribute, usernameEscaped),
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Deliberately skip errors on the search request so that we can jump to other search settings
|
||||
// if any issue arise with the current one.
|
||||
sr, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sr.Entries) == 1 {
|
||||
found = true
|
||||
userDN = sr.Entries[0].DN
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", errUserNotFound
|
||||
}
|
||||
|
||||
return userDN, nil
|
||||
}
|
||||
|
||||
// Get a list of group names for specified user from LDAP/AD
|
||||
func getGroupsByUser(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string {
|
||||
func getGroups(userDN string, conn *ldap.Conn, settings []portainer.LDAPGroupSearchSettings) []string {
|
||||
groups := make([]string, 0)
|
||||
userDNEscaped := ldap.EscapeFilter(userDN)
|
||||
|
||||
@@ -296,18 +179,9 @@ func (*Service) TestConnectivity(settings *portainer.LDAPSettings) error {
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
if !settings.AnonymousMode {
|
||||
err = connection.Bind(settings.ReaderDN, settings.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
err = connection.UnauthenticatedBind("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = connection.Bind(settings.ReaderDN, settings.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -513,12 +513,6 @@ type (
|
||||
AutoCreateUsers bool `json:"AutoCreateUsers" example:"true"`
|
||||
}
|
||||
|
||||
// LDAPUser represents a LDAP user
|
||||
LDAPUser struct {
|
||||
Name string
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// LicenseInformation represents information about an extension license
|
||||
LicenseInformation struct {
|
||||
LicenseKey string `json:"LicenseKey,omitempty"`
|
||||
@@ -1301,8 +1295,6 @@ type (
|
||||
AuthenticateUser(username, password string, settings *LDAPSettings) error
|
||||
TestConnectivity(settings *LDAPSettings) error
|
||||
GetUserGroups(username string, settings *LDAPSettings) ([]string, error)
|
||||
SearchGroups(settings *LDAPSettings) ([]LDAPUser, error)
|
||||
SearchUsers(settings *LDAPSettings) ([]string, error)
|
||||
}
|
||||
|
||||
// OAuthService represents a service used to authenticate users using OAuth
|
||||
|
||||
@@ -721,6 +721,10 @@ a[ng-click] {
|
||||
.multiSelect .multiSelectItem:hover,
|
||||
.multiSelect .multiSelectGroup:hover {
|
||||
border-color: var(--grey-3);
|
||||
}
|
||||
|
||||
.multiSelect .multiSelectItem:hover,
|
||||
.multiSelect .multiSelectGroup:hover {
|
||||
background-image: var(--bg-image-multiselect) !important;
|
||||
color: var(--white-color) !important;
|
||||
}
|
||||
|
||||
@@ -246,6 +246,12 @@ json-tree .branch-preview {
|
||||
.pagination > li > span:focus {
|
||||
background-color: var(--bg-pagination-hover-color);
|
||||
border-color: var(--border-pagination-hover-color);
|
||||
}
|
||||
|
||||
.pagination > li > a:hover,
|
||||
.pagination > li > span:hover,
|
||||
.pagination > li > a:focus,
|
||||
.pagination > li > span:focus {
|
||||
color: var(--text-pagination-span-hover-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class KubernetesDeployController {
|
||||
RepositoryUsername: '',
|
||||
RepositoryPassword: '',
|
||||
AdditionalFiles: [],
|
||||
ComposeFilePathInRepository: '',
|
||||
ComposeFilePathInRepository: 'deployment.yml',
|
||||
RepositoryAutomaticUpdates: true,
|
||||
RepositoryMechanism: RepositoryMechanismTypes.INTERVAL,
|
||||
RepositoryFetchInterval: '5m',
|
||||
|
||||
@@ -146,13 +146,14 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
ng-data-cy="k8sNamespaceCreate-loadBalancerQuotaToggle"
|
||||
label="Load Balancer quota"
|
||||
name="k8s-resourcepool-Lbquota"
|
||||
feature="ctrl.LBQuotaFeatureId"
|
||||
ng-model="lbquota"
|
||||
></por-switch-field>
|
||||
<label class="control-label text-left">
|
||||
Load Balancer quota
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
|
||||
<span class="text-muted small" style="margin-left: 15px;">
|
||||
<i class="fa fa-user" aria-hidden="true"></i>
|
||||
This feature is available in <a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-lbquota" target="_blank"> Portainer Business Edition</a>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
@@ -388,13 +389,15 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
ng-data-cy="k8sNamespaceCreate-enableQuotaToggle"
|
||||
label="Enable quota"
|
||||
name="k8s-resourcepool-storagequota"
|
||||
feature="ctrl.StorageQuotaFeatureId"
|
||||
ng-model="storagequota"
|
||||
></por-switch-field>
|
||||
<label class="control-label text-left">
|
||||
Enable quota
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" disabled /><i></i> </label>
|
||||
<span class="text-muted small" style="margin-left: 15px;">
|
||||
<i class="fa fa-user" aria-hidden="true"></i>
|
||||
This feature is available in
|
||||
<a href="https://www.portainer.io/business-upsell?from=k8s-resourcepool-storagequota" target="_blank"> Portainer Business Edition</a>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
||||
@@ -16,7 +16,6 @@ import KubernetesFormValidationHelper from 'Kubernetes/helpers/formValidationHel
|
||||
import { KubernetesIngressClassTypes } from 'Kubernetes/ingress/constants';
|
||||
import KubernetesResourceQuotaConverter from 'Kubernetes/converters/resourceQuota';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { K8S_RESOURCE_POOL_LB_QUOTA, K8S_RESOURCE_POOL_STORAGE_QUOTA } from '@/portainer/feature-flags/feature-ids';
|
||||
|
||||
class KubernetesResourcePoolController {
|
||||
/* #region CONSTRUCTOR */
|
||||
@@ -61,9 +60,6 @@ class KubernetesResourcePoolController {
|
||||
this.IngressClassTypes = KubernetesIngressClassTypes;
|
||||
this.ResourceQuotaDefaults = KubernetesResourceQuotaDefaults;
|
||||
|
||||
this.LBQuotaFeatureId = K8S_RESOURCE_POOL_LB_QUOTA;
|
||||
this.StorageQuotaFeatureId = K8S_RESOURCE_POOL_STORAGE_QUOTA;
|
||||
|
||||
this.updateResourcePoolAsync = this.updateResourcePoolAsync.bind(this);
|
||||
this.getEvents = this.getEvents.bind(this);
|
||||
}
|
||||
|
||||
@@ -6,18 +6,9 @@ export default class BoxSelectorItemController {
|
||||
this.limitedToBE = false;
|
||||
}
|
||||
|
||||
handleChange(value) {
|
||||
this.formCtrl.$setValidity(this.radioName, !this.limitedToBE, this.formCtrl);
|
||||
this.onChange(value);
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
if (this.option.feature) {
|
||||
this.limitedToBE = this.featureService.isLimitedToBE(this.option.feature);
|
||||
}
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.formCtrl.$setValidity(this.radioName, true, this.formCtrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
ng-value="$ctrl.option.value"
|
||||
ng-disabled="$ctrl.disabled"
|
||||
/>
|
||||
<label for="{{ $ctrl.option.id }}" ng-click="$ctrl.handleChange($ctrl.option.value)">
|
||||
<label for="{{ $ctrl.option.id }}" ng-click="$ctrl.onChange($ctrl.option.value, $ctrl.limitedToBE)">
|
||||
<i class="fas fa-briefcase limited-icon" ng-if="$ctrl.limitedToBE"></i>
|
||||
|
||||
<div class="boxselector_header">
|
||||
|
||||
@@ -7,9 +7,6 @@ import controller from './box-selector-item.controller';
|
||||
angular.module('portainer.app').component('boxSelectorItem', {
|
||||
templateUrl: './box-selector-item.html',
|
||||
controller,
|
||||
require: {
|
||||
formCtrl: '^^form',
|
||||
},
|
||||
bindings: {
|
||||
radioName: '@',
|
||||
isChecked: '<',
|
||||
|
||||
@@ -2,18 +2,13 @@
|
||||
border-color: var(--BE-only);
|
||||
}
|
||||
|
||||
.form-control.limited-be.no-border {
|
||||
border-color: var(--border-form-control-color);
|
||||
}
|
||||
|
||||
button.limited-be {
|
||||
background-color: var(--BE-only);
|
||||
border-color: var(--BE-only);
|
||||
}
|
||||
|
||||
ng-form.limited-be,
|
||||
form.limited-be,
|
||||
div.limited-be {
|
||||
form.limited-be {
|
||||
border: solid 1px var(--BE-only);
|
||||
padding: 10px;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -19,7 +19,7 @@ export function limitedFeatureDirective(featureService) {
|
||||
}
|
||||
|
||||
const limitedFeatureAttrs = Object.keys(attrs)
|
||||
.filter((attr) => attr.startsWith(BASENAME) && attr !== `${BASENAME}Dir`)
|
||||
.filter((attr) => attr.startsWith(BASENAME) && attr !== BASENAME)
|
||||
.map((attr) => [_.kebabCase(attr.replace(BASENAME, '')), attrs[attr]]);
|
||||
|
||||
const state = featureService.selectShow(featureId);
|
||||
|
||||
@@ -48,9 +48,6 @@ export default class OAuthSettingsController {
|
||||
if (providerId === 'microsoft' && this.state.microsoftTenantID !== '') {
|
||||
this.onMicrosoftTenantIDChange();
|
||||
}
|
||||
} else {
|
||||
this.settings.ClientID = '';
|
||||
this.settings.ClientSecret = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,6 @@
|
||||
limited-feature-dir="{{::$ctrl.limitedFeature}}"
|
||||
limited-feature-class="limited-be"
|
||||
limited-feature-disabled
|
||||
limited-feature-tabindex="-1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -203,7 +202,7 @@
|
||||
placeholder="xxxxxxxxxxxxxxxxxxxx"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -222,7 +221,7 @@
|
||||
autocomplete="new-password"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -237,15 +236,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_authorization_uri"
|
||||
ng-model="$ctrl.settings.AuthorizationURI"
|
||||
placeholder="https://example.com/oauth/authorize"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_authorization_uri" ng-model="$ctrl.settings.AuthorizationURI" placeholder="https://example.com/oauth/authorize" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -255,15 +246,7 @@
|
||||
<portainer-tooltip position="bottom" message="URL used by Portainer to exchange a valid OAuth authentication code for an access token"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_access_token_uri"
|
||||
ng-model="$ctrl.settings.AccessTokenURI"
|
||||
placeholder="https://example.com/oauth/token"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_access_token_uri" ng-model="$ctrl.settings.AccessTokenURI" placeholder="https://example.com/oauth/token" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -273,15 +256,7 @@
|
||||
<portainer-tooltip position="bottom" message="URL used by Portainer to retrieve information about the authenticated user"></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_resource_uri"
|
||||
ng-model="$ctrl.settings.ResourceURI"
|
||||
placeholder="https://example.com/user"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_resource_uri" ng-model="$ctrl.settings.ResourceURI" placeholder="https://example.com/user" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,15 +269,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_redirect_uri"
|
||||
ng-model="$ctrl.settings.RedirectURI"
|
||||
placeholder="http://yourportainer.com/"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_redirect_uri" ng-model="$ctrl.settings.RedirectURI" placeholder="http://yourportainer.com/" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -315,14 +282,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_logout_url"
|
||||
ng-model="$ctrl.settings.LogoutURI"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_logout_url" ng-model="$ctrl.settings.LogoutURI" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -335,15 +295,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_user_identifier"
|
||||
ng-model="$ctrl.settings.UserIdentifier"
|
||||
placeholder="id"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_user_identifier" ng-model="$ctrl.settings.UserIdentifier" placeholder="id" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -356,15 +308,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="oauth_scopes"
|
||||
ng-model="$ctrl.settings.Scopes"
|
||||
placeholder="id,email,name"
|
||||
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
|
||||
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
|
||||
/>
|
||||
<input type="text" class="form-control" id="oauth_scopes" ng-model="$ctrl.settings.Scopes" placeholder="id,email,name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,36 +35,14 @@
|
||||
<div class="form-group">
|
||||
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
|
||||
AD Controller
|
||||
<button
|
||||
type="button"
|
||||
class="label label-default interactive"
|
||||
style="border: 0;"
|
||||
ng-click="$ctrl.addLDAPUrl()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
|
||||
</button>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="ldap_url"
|
||||
ng-model="$ctrl.settings.URLs[$index]"
|
||||
placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<button
|
||||
ng-if="$index > 0"
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.removeLDAPUrl($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
|
||||
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -84,8 +62,6 @@
|
||||
ng-model="$ctrl.settings.ReaderDN"
|
||||
placeholder="reader@domain.tld"
|
||||
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,16 +72,7 @@
|
||||
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="ldap_password"
|
||||
ng-model="$ctrl.settings.Password"
|
||||
placeholder="password"
|
||||
autocomplete="new-password"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +81,6 @@
|
||||
settings="$ctrl.settings"
|
||||
state="$ctrl.state"
|
||||
connectivity-check="$ctrl.connectivityCheck"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-connectivity-check>
|
||||
|
||||
<ldap-settings-security
|
||||
@@ -123,7 +89,6 @@
|
||||
tlsca-cert="$ctrl.tlscaCert"
|
||||
upload-in-progress="$ctrl.state.uploadInProgress"
|
||||
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-security>
|
||||
|
||||
<ldap-connectivity-check
|
||||
@@ -131,7 +96,6 @@
|
||||
settings="$ctrl.settings"
|
||||
state="$ctrl.state"
|
||||
connectivity-check="$ctrl.connectivityCheck"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-connectivity-check>
|
||||
|
||||
<ldap-user-search
|
||||
@@ -141,7 +105,6 @@
|
||||
domain-suffix="{{ $ctrl.domainSuffix }}"
|
||||
base-filter="(objectClass=user)"
|
||||
on-search-click="($ctrl.searchUsers)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-user-search>
|
||||
|
||||
<ldap-group-search
|
||||
@@ -150,8 +113,7 @@
|
||||
domain-suffix="{{ $ctrl.domainSuffix }}"
|
||||
base-filter="(objectClass=group)"
|
||||
on-search-click="($ctrl.searchGroups)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-group-search>
|
||||
|
||||
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login>
|
||||
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>
|
||||
</ng-form>
|
||||
|
||||
@@ -4,6 +4,5 @@ export const ldapConnectivityCheck = {
|
||||
settings: '<',
|
||||
state: '<',
|
||||
connectivityCheck: '<',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
ng-disabled="($ctrl.state.connectivityCheckInProgress) || (!$ctrl.settings.URLs.length) || ((!$ctrl.settings.ReaderDN || !$ctrl.settings.Password) && !$ctrl.settings.AnonymousMode)"
|
||||
ng-click="$ctrl.connectivityCheck()"
|
||||
button-spinner="$ctrl.state.connectivityCheckInProgress"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<span ng-hide="$ctrl.state.connectivityCheckInProgress">Test connectivity</span>
|
||||
<span ng-show="$ctrl.state.connectivityCheckInProgress">Testing connectivity...</span>
|
||||
|
||||
@@ -6,6 +6,6 @@ export const ldapCustomGroupSearch = {
|
||||
bindings: {
|
||||
settings: '=',
|
||||
onSearchClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
limitedFeature: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="col-sm-12 form-section-title" style="float: initial;">
|
||||
Group search configurations
|
||||
Teams auto-population configurations
|
||||
</div>
|
||||
|
||||
<rd-widget ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)" style="display: block; margin-bottom: 10px;">
|
||||
@@ -55,14 +55,13 @@
|
||||
class="btn btm-sm btn-primary"
|
||||
type="button"
|
||||
ng-click="$ctrl.search()"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
|
||||
limited-feature-tabindex="-1"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeature }}"
|
||||
limited-feature-disabled
|
||||
limited-feature-class="limited-be"
|
||||
>
|
||||
Display User/Group matching
|
||||
</button>
|
||||
<be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
|
||||
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ export const ldapCustomUserSearch = {
|
||||
bindings: {
|
||||
settings: '=',
|
||||
onSearchClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
limitedFeature: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -55,14 +55,13 @@
|
||||
class="btn btm-sm btn-primary"
|
||||
type="button"
|
||||
ng-click="$ctrl.search()"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeature }}"
|
||||
limited-feature-disabled
|
||||
limited-feature-class="limited-be"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
Display Users
|
||||
</button>
|
||||
<be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
|
||||
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@ export const ldapGroupSearchItem = {
|
||||
baseFilter: '@',
|
||||
|
||||
onRemoveClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
<span class="text-muted small">
|
||||
Extra search configuration
|
||||
</span>
|
||||
<button
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.onRemoveClick($ctrl.index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -20,7 +14,6 @@
|
||||
suffix="{{ $ctrl.domainSuffix }}"
|
||||
ng-model="$ctrl.config.GroupBaseDN"
|
||||
on-change="($ctrl.onChangeBaseDN)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-dn-builder>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -44,34 +37,16 @@
|
||||
<rd-widget-body>
|
||||
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.groups">
|
||||
<div class="col-sm-4">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="entry.type"
|
||||
ng-change="$ctrl.onGroupsChange()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onGroupsChange()">
|
||||
<option value="ou">OU Name</option>
|
||||
<option value="cn">Folder Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<input
|
||||
class="form-control"
|
||||
ng-model="entry.value"
|
||||
ng-change="$ctrl.onGroupsChange()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onGroupsChange()" />
|
||||
</div>
|
||||
<div class="col-sm-3 text-right">
|
||||
<button
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.removeGroup($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeGroup($index)">
|
||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,5 @@ export const ldapGroupSearch = {
|
||||
baseFilter: '@',
|
||||
|
||||
onSearchClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="col-sm-12 form-section-title" style="float: initial;">
|
||||
Group search configurations
|
||||
Teams auto-population configurations
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px;" ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)">
|
||||
@@ -9,24 +9,17 @@
|
||||
index="$index"
|
||||
base-filter="{{ $ctrl.baseFilter }}"
|
||||
on-remove-click="($ctrl.onRemoveClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-group-search-item>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
class="label label-default interactive"
|
||||
style="border: 0;"
|
||||
ng-click="$ctrl.onAddClick()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px;">
|
||||
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
|
||||
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
|
||||
Display User/Group matching
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EXTERNAL_AUTH_LDAP } from '@/portainer/feature-flags/feature-ids';
|
||||
|
||||
export default class LdapSettingsCustomController {
|
||||
constructor() {
|
||||
this.limitedFeatureId = EXTERNAL_AUTH_LDAP;
|
||||
this.limitedFeature = EXTERNAL_AUTH_LDAP;
|
||||
}
|
||||
|
||||
addLDAPUrl() {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
class="label label-default interactive"
|
||||
style="border: 0;"
|
||||
ng-click="$ctrl.addLDAPUrl()"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeatureId }}"
|
||||
limited-feature-dir="{{ $ctrl.limitedFeature }}"
|
||||
limited-feature-disabled
|
||||
limited-feature-class="limited-be"
|
||||
>
|
||||
@@ -38,7 +38,7 @@
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
|
||||
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" required />
|
||||
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
|
||||
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
@@ -105,15 +105,13 @@
|
||||
style="margin-top: 5px;"
|
||||
settings="$ctrl.settings.SearchSettings"
|
||||
on-search-click="($ctrl.onSearchUsersClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
limited-feature="$ctrl.limitedFeature"
|
||||
></ldap-custom-user-search>
|
||||
<ldap-custom-group-search
|
||||
style="margin-top: 5px;"
|
||||
settings="$ctrl.settings.GroupSearchSettings"
|
||||
on-search-click="($ctrl.onSearchGroupsClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
limited-feature="$ctrl.limitedFeature"
|
||||
></ldap-custom-group-search>
|
||||
|
||||
<div limited-feature-dir="{{ $ctrl.limitedFeatureId }}" limited-feature-class="limited-be">
|
||||
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId" show-be-indicator-if-needed="true"></ldap-settings-test-login>
|
||||
</div>
|
||||
<ldap-settings-test-login settings="$ctrl.settings" limited-feature="$ctrl.limitedFeature"></ldap-settings-test-login>
|
||||
|
||||
@@ -11,6 +11,5 @@ export const ldapSettingsDnBuilder = {
|
||||
// suffix: string (dc=,dc=,)
|
||||
suffix: '@',
|
||||
label: '@',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<div class="form-group ldap-dn-builder">
|
||||
<div class="col-sm-12" style="margin-bottom: 5px;">
|
||||
<label class="control-label text-left">{{ $ctrl.label || 'DN entries' }}</label>
|
||||
<button
|
||||
type="button"
|
||||
class="label label-default interactive"
|
||||
style="margin-left: 10px; border: 0;"
|
||||
ng-click="$ctrl.addEntry()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addEntry()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another entry
|
||||
</button>
|
||||
</div>
|
||||
@@ -17,48 +10,22 @@
|
||||
<rd-widget-body>
|
||||
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.entries">
|
||||
<div class="col-sm-4">
|
||||
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onEntriesChange()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
|
||||
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onEntriesChange()">
|
||||
<option value="ou">OU Name</option>
|
||||
<option value="cn">Folder Name</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<input
|
||||
class="form-control"
|
||||
ng-model="entry.value"
|
||||
ng-change="$ctrl.onEntriesChange()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onEntriesChange()" />
|
||||
</div>
|
||||
<div class="col-sm-3 text-right">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
ng-disabled="$first"
|
||||
ng-click="$ctrl.moveUp($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$first" ng-click="$ctrl.moveUp($index)">
|
||||
<i class="fa fa-arrow-up" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
type="button"
|
||||
ng-disabled="$last"
|
||||
ng-click="$ctrl.moveDown($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$last" ng-click="$ctrl.moveDown($index)">
|
||||
<i class="fa fa-arrow-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.removeEntry($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeEntry($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,5 @@ export const ldapSettingsGroupDnBuilder = {
|
||||
// index: int >= 0
|
||||
index: '<',
|
||||
onRemoveClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,25 +3,10 @@
|
||||
Group Name
|
||||
</label>
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="group-name-input"
|
||||
ng-model="$ctrl.groupName"
|
||||
ng-change="$ctrl.onGroupNameChange()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input type="text" class="form-control" id="group-name-input" ng-model="$ctrl.groupName" ng-change="$ctrl.onGroupNameChange()" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger btn-sm"
|
||||
ng-if="$ctrl.onRemoveClick"
|
||||
ng-click="$ctrl.onRemoveClick()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button type="button" class="btn btn-danger btn-sm" ng-if="$ctrl.onRemoveClick" ng-click="$ctrl.onRemoveClick()">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -33,5 +18,4 @@
|
||||
suffix="{{ $ctrl.suffix }}"
|
||||
on-change="($ctrl.onEntriesChange)"
|
||||
on-remove-click="($ctrl.removeGroup)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-dn-builder>
|
||||
|
||||
@@ -27,36 +27,14 @@
|
||||
<div class="form-group">
|
||||
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
|
||||
LDAP Server
|
||||
<button
|
||||
type="button"
|
||||
class="label label-default interactive"
|
||||
style="border: 0;"
|
||||
ng-click="$ctrl.addLDAPUrl()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
|
||||
</button>
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="ldap_url"
|
||||
ng-model="$ctrl.settings.URLs[$index]"
|
||||
placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<button
|
||||
ng-if="$index > 0"
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.removeLDAPUrl($index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
|
||||
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -70,15 +48,7 @@
|
||||
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="anonymous_mode"
|
||||
ng-model="$ctrl.settings.AnonymousMode"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/><i></i>
|
||||
</label>
|
||||
<label class="switch"> <input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !Anonymous mode-->
|
||||
@@ -97,8 +67,6 @@
|
||||
ng-model="$ctrl.settings.ReaderDN"
|
||||
placeholder="cn=user,dc=domain,dc=tld"
|
||||
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,16 +77,7 @@
|
||||
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="ldap_password"
|
||||
ng-model="$ctrl.settings.Password"
|
||||
placeholder="password"
|
||||
autocomplete="new-password"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,15 +87,7 @@
|
||||
Domain root
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="ldap_domain_root"
|
||||
ng-model="$ctrl.domainSuffix"
|
||||
placeholder="dc=domain,dc=tld"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
/>
|
||||
<input type="text" class="form-control" id="ldap_domain_root" ng-model="$ctrl.domainSuffix" placeholder="dc=domain,dc=tld" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -145,7 +96,6 @@
|
||||
settings="$ctrl.settings"
|
||||
state="$ctrl.state"
|
||||
connectivity-check="$ctrl.connectivityCheck"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-connectivity-check>
|
||||
|
||||
<ldap-settings-security
|
||||
@@ -154,7 +104,6 @@
|
||||
tlsca-cert="$ctrl.tlscaCert"
|
||||
upload-in-progress="$ctrl.state.uploadInProgress"
|
||||
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-security>
|
||||
|
||||
<ldap-connectivity-check
|
||||
@@ -162,7 +111,6 @@
|
||||
settings="$ctrl.settings"
|
||||
state="$ctrl.state"
|
||||
connectivity-check="$ctrl.connectivityCheck"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-connectivity-check>
|
||||
|
||||
<ldap-user-search
|
||||
@@ -171,7 +119,6 @@
|
||||
domain-suffix="{{ $ctrl.domainSuffix }}"
|
||||
base-filter="(objectClass=inetOrgPerson)"
|
||||
on-search-click="($ctrl.onSearchUsersClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-user-search>
|
||||
|
||||
<ldap-group-search
|
||||
@@ -180,8 +127,7 @@
|
||||
domain-suffix="{{ $ctrl.domainSuffix }}"
|
||||
base-filter="(objectClass=groupOfNames)"
|
||||
on-search-click="($ctrl.onSearchGroupsClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-group-search>
|
||||
|
||||
<ldap-settings-test-login settings="$ctrl.settings" limited-feature-id="$ctrl.limitedFeatureId"></ldap-settings-test-login>
|
||||
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>
|
||||
</ng-form>
|
||||
|
||||
@@ -6,6 +6,5 @@ export const ldapSettingsSecurity = {
|
||||
onTlscaCertChange: '<',
|
||||
uploadInProgress: '<',
|
||||
title: '@',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<label class="switch">
|
||||
<input type="checkbox" ng-model="$ctrl.settings.StartTLS" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
|
||||
</label>
|
||||
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.StartTLS" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !starttls -->
|
||||
@@ -26,9 +24,7 @@
|
||||
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the LDAP server."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<label class="switch">
|
||||
<input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLS" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
|
||||
</label>
|
||||
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLS" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-checkbox -->
|
||||
@@ -40,9 +36,7 @@
|
||||
<portainer-tooltip position="bottom" message="Skip the verification of the server TLS certificate. Not recommended on unsecured networks."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<label class="switch">
|
||||
<input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLSSkipVerify" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" /><i></i>
|
||||
</label>
|
||||
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLSSkipVerify" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-skip-verify -->
|
||||
@@ -51,16 +45,7 @@
|
||||
<div class="form-group" ng-if="$ctrl.settings.TLSConfig.TLS || ($ctrl.settings.StartTLS && !$ctrl.settings.TLSConfig.TLSSkipVerify)">
|
||||
<label class="col-sm-3 control-label text-left">TLS CA certificate</label>
|
||||
<div class="col-sm-9">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
ngf-select="$ctrl.onTlscaCertChange($file)"
|
||||
ng-model="$ctrl.tlscaCert"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
Select file
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onTlscaCertChange($file)" ng-model="$ctrl.tlscaCert">Select file</button>
|
||||
<span style="margin-left: 5px;">
|
||||
{{ $ctrl.tlscaCert.name }}
|
||||
<i class="fa fa-check green-icon" ng-if="$ctrl.tlscaCert && $ctrl.tlscaCert === $ctrl.settings.TLSConfig.TLSCACert" aria-hidden="true"></i>
|
||||
|
||||
@@ -5,7 +5,6 @@ export const ldapSettingsTestLogin = {
|
||||
controller,
|
||||
bindings: {
|
||||
settings: '=',
|
||||
limitedFeatureId: '<',
|
||||
showBeIndicatorIfNeeded: '<',
|
||||
limitedFeature: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<label for="ldap_test_username" style="font-size: 0.9em; margin-right: 5px;">
|
||||
Username
|
||||
</label>
|
||||
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1" />
|
||||
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" limited-feature-dir="{{::$ctrl.limitedFeature}}" limited-feature-class="limited-be" />
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin: 0;">
|
||||
@@ -19,8 +19,8 @@
|
||||
id="ldap_test_password"
|
||||
ng-model="$ctrl.password"
|
||||
autocomplete="new-password"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeature}}"
|
||||
limited-feature-class="limited-be"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -30,9 +30,8 @@
|
||||
class="btn btn-primary"
|
||||
ng-disabled="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING || !$ctrl.username || !$ctrl.password"
|
||||
ng-click="$ctrl.testLogin($ctrl.username, $ctrl.password)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeature}}"
|
||||
limited-feature-disabled
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<span ng-if="$ctrl.state.testStatus !== $ctrl.TEST_STATUS.LOADING">Test</span>
|
||||
<span ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING">Testing...</span>
|
||||
@@ -41,5 +40,5 @@
|
||||
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.FAILURE" class="fa fa-times red-icon"></i>
|
||||
</div>
|
||||
|
||||
<be-feature-indicator ng-if="$ctrl.showBeIndicatorIfNeeded" feature="$ctrl.limitedFeatureId" class="space-left"></be-feature-indicator>
|
||||
<be-feature-indicator feature="$ctrl.limitedFeature" class="space-left"></be-feature-indicator>
|
||||
</div>
|
||||
|
||||
@@ -10,13 +10,7 @@
|
||||
Server Type
|
||||
</div>
|
||||
|
||||
<box-selector
|
||||
style="margin-bottom: 0;"
|
||||
radio-name="ldap-server-type-selector"
|
||||
ng-model="$ctrl.settings.ServerType"
|
||||
options="$ctrl.boxSelectorOptions"
|
||||
on-change="($ctrl.onChangeServerType)"
|
||||
></box-selector>
|
||||
<box-selector style="margin-bottom: 0;" ng-model="$ctrl.settings.ServerType" options="$ctrl.boxSelectorOptions" on-change="($ctrl.onChangeServerType)"></box-selector>
|
||||
|
||||
<ldap-settings-custom
|
||||
ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.CUSTOM"
|
||||
|
||||
@@ -10,6 +10,5 @@ export const ldapUserSearchItem = {
|
||||
domainSuffix: '@',
|
||||
baseFilter: '@',
|
||||
onRemoveClick: '<',
|
||||
limitedFeatureId: '<',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,14 +4,7 @@
|
||||
<span class="text-muted small">
|
||||
Extra search configuration
|
||||
</span>
|
||||
<button
|
||||
ng-if="$ctrl.index > 0"
|
||||
class="btn btn-sm btn-danger"
|
||||
type="button"
|
||||
ng-click="$ctrl.onRemoveClick($ctrl.index)"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button ng-if="$ctrl.index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -23,23 +16,8 @@
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-model="$ctrl.config.UserNameAttribute"
|
||||
uib-btn-radio="'sAMAccountName'"
|
||||
style="margin-left: 0px;"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>username</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
ng-model="$ctrl.config.UserNameAttribute"
|
||||
uib-btn-radio="'userPrincipalName'"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>user@domainname</button
|
||||
>
|
||||
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'sAMAccountName'" style="margin-left: 0px;">username</button>
|
||||
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'userPrincipalName'">user@domainname</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,20 +37,12 @@
|
||||
label="User Search Path (optional)"
|
||||
suffix="{{ $ctrl.domainSuffix }}"
|
||||
on-change="($ctrl.onBaseDNChange)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-dn-builder>
|
||||
|
||||
<div class="form-group no-margin-last-child">
|
||||
<div class="col-sm-12" style="margin-bottom: 5px;">
|
||||
<label class="control-label text-left">Allowed Groups (optional)</label>
|
||||
<button
|
||||
type="button"
|
||||
class="label label-default interactive"
|
||||
style="margin-left: 10px; border: 0;"
|
||||
ng-click="$ctrl.addGroup()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addGroup()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another group
|
||||
</button>
|
||||
</div>
|
||||
@@ -86,7 +56,6 @@
|
||||
suffix="{{ $ctrl.domainSuffix }}"
|
||||
on-change="($ctrl.onGroupChange)"
|
||||
on-remove-click="($ctrl.removeGroup)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-settings-group-dn-builder>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
|
||||
@@ -8,7 +8,6 @@ export const ldapUserSearch = {
|
||||
domainSuffix: '@',
|
||||
showUsernameFormat: '<',
|
||||
baseFilter: '@',
|
||||
limitedFeatureId: '<',
|
||||
|
||||
onSearchClick: '<',
|
||||
},
|
||||
|
||||
@@ -10,24 +10,17 @@
|
||||
show-username-format="$ctrl.showUsernameFormat"
|
||||
base-filter="{{ $ctrl.baseFilter }}"
|
||||
on-remove-click="($ctrl.onRemoveClick)"
|
||||
limited-feature-id="$ctrl.limitedFeatureId"
|
||||
></ldap-user-search-item>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
class="label label-default interactive"
|
||||
style="border: 0;"
|
||||
ng-click="$ctrl.onAddClick()"
|
||||
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
|
||||
limited-feature-tabindex="-1"
|
||||
>
|
||||
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add user search configuration
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-12" style="margin-top: 10px;">
|
||||
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
|
||||
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
|
||||
Display Users
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user