mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Case insensitive behavior for LDAP (#4238)
This commit is contained in:
		| @@ -15,6 +15,11 @@ DEPRECATIONS/CHANGES: | ||||
|    accommodate this as best as possible, and users of other tools may have to | ||||
|    make adjustments, but in the end we felt that the ends did not justify the | ||||
|    means and we needed to prioritize security over operational convenience. | ||||
|  * LDAP auth method case sensitivity: We now treat usernames and groups | ||||
|    configured locally for policy assignment in a case insensitive fashion by | ||||
|    default. Existing configurations will continue to work as they do now; | ||||
|    however, the next time a configuration is written `case_sensitive_names` | ||||
|    will need to be explicitly set to `true`. | ||||
|  | ||||
| FEATURES: | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/go-ldap/ldap" | ||||
| @@ -173,8 +174,13 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri | ||||
| 	} | ||||
|  | ||||
| 	var allGroups []string | ||||
| 	canonicalUsername := username | ||||
| 	cs := *cfg.CaseSensitiveNames | ||||
| 	if !cs { | ||||
| 		canonicalUsername = strings.ToLower(username) | ||||
| 	} | ||||
| 	// Import the custom added groups from ldap backend | ||||
| 	user, err := b.User(ctx, req.Storage, username) | ||||
| 	user, err := b.User(ctx, req.Storage, canonicalUsername) | ||||
| 	if err == nil && user != nil && user.Groups != nil { | ||||
| 		if b.Logger().IsDebug() { | ||||
| 			b.Logger().Debug("adding local groups", "num_local_groups", len(user.Groups), "local_groups", user.Groups) | ||||
| @@ -184,9 +190,18 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri | ||||
| 	// Merge local and LDAP groups | ||||
| 	allGroups = append(allGroups, ldapGroups...) | ||||
|  | ||||
| 	canonicalGroups := allGroups | ||||
| 	// If not case sensitive, lowercase all | ||||
| 	if !cs { | ||||
| 		canonicalGroups = make([]string, len(allGroups)) | ||||
| 		for i, v := range allGroups { | ||||
| 			canonicalGroups[i] = strings.ToLower(v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Retrieve policies | ||||
| 	var policies []string | ||||
| 	for _, groupName := range allGroups { | ||||
| 	for _, groupName := range canonicalGroups { | ||||
| 		group, err := b.Group(ctx, req.Storage, groupName) | ||||
| 		if err == nil && group != nil { | ||||
| 			policies = append(policies, group.Policies...) | ||||
|   | ||||
| @@ -31,6 +31,182 @@ func createBackendWithStorage(t *testing.T) (*backend, logical.Storage) { | ||||
| 	return b, config.StorageView | ||||
| } | ||||
|  | ||||
| func TestLdapAuthBackend_CaseSensitivity(t *testing.T) { | ||||
| 	var resp *logical.Response | ||||
| 	var err error | ||||
| 	b, storage := createBackendWithStorage(t) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	testVals := func(caseSensitive bool) { | ||||
| 		// Clear storage | ||||
| 		userList, err := storage.List(ctx, "user/") | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		for _, user := range userList { | ||||
| 			err = storage.Delete(ctx, "user/"+user) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
| 		groupList, err := storage.List(ctx, "group/") | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		for _, group := range groupList { | ||||
| 			err = storage.Delete(ctx, "group/"+group) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		configReq := &logical.Request{ | ||||
| 			Path:      "config", | ||||
| 			Operation: logical.ReadOperation, | ||||
| 			Storage:   storage, | ||||
| 		} | ||||
| 		resp, err = b.HandleRequest(ctx, configReq) | ||||
| 		if err != nil || (resp != nil && resp.IsError()) { | ||||
| 			t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 		} | ||||
| 		if resp == nil { | ||||
| 			t.Fatal("nil response") | ||||
| 		} | ||||
| 		if resp.Data["case_sensitive_names"].(bool) != caseSensitive { | ||||
| 			t.Fatalf("expected case sensitivity %t, got %t", caseSensitive, resp.Data["case_sensitive_names"].(bool)) | ||||
| 		} | ||||
|  | ||||
| 		groupReq := &logical.Request{ | ||||
| 			Operation: logical.UpdateOperation, | ||||
| 			Data: map[string]interface{}{ | ||||
| 				"policies": "grouppolicy", | ||||
| 			}, | ||||
| 			Path:    "groups/EngineerS", | ||||
| 			Storage: storage, | ||||
| 		} | ||||
| 		resp, err = b.HandleRequest(ctx, groupReq) | ||||
| 		if err != nil || (resp != nil && resp.IsError()) { | ||||
| 			t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 		} | ||||
| 		keys, err := storage.List(ctx, "group/") | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		switch caseSensitive { | ||||
| 		case true: | ||||
| 			if keys[0] != "EngineerS" { | ||||
| 				t.Fatalf("bad: %s", keys[0]) | ||||
| 			} | ||||
| 		default: | ||||
| 			if keys[0] != "engineers" { | ||||
| 				t.Fatalf("bad: %s", keys[0]) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		userReq := &logical.Request{ | ||||
| 			Operation: logical.UpdateOperation, | ||||
| 			Data: map[string]interface{}{ | ||||
| 				"groups":   "EngineerS", | ||||
| 				"policies": "userpolicy", | ||||
| 			}, | ||||
| 			Path:    "users/teSlA", | ||||
| 			Storage: storage, | ||||
| 		} | ||||
| 		resp, err = b.HandleRequest(ctx, userReq) | ||||
| 		if err != nil || (resp != nil && resp.IsError()) { | ||||
| 			t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 		} | ||||
| 		keys, err = storage.List(ctx, "user/") | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 		switch caseSensitive { | ||||
| 		case true: | ||||
| 			if keys[0] != "teSlA" { | ||||
| 				t.Fatalf("bad: %s", keys[0]) | ||||
| 			} | ||||
| 		default: | ||||
| 			if keys[0] != "tesla" { | ||||
| 				t.Fatalf("bad: %s", keys[0]) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if caseSensitive { | ||||
| 			// The online test server is actually case sensitive so we need to | ||||
| 			// write again so it works | ||||
| 			userReq = &logical.Request{ | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Data: map[string]interface{}{ | ||||
| 					"groups":   "EngineerS", | ||||
| 					"policies": "userpolicy", | ||||
| 				}, | ||||
| 				Path:    "users/tesla", | ||||
| 				Storage: storage, | ||||
| 			} | ||||
| 			resp, err = b.HandleRequest(ctx, userReq) | ||||
| 			if err != nil || (resp != nil && resp.IsError()) { | ||||
| 				t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		loginReq := &logical.Request{ | ||||
| 			Operation: logical.UpdateOperation, | ||||
| 			Path:      "login/tesla", | ||||
| 			Data: map[string]interface{}{ | ||||
| 				"password": "password", | ||||
| 			}, | ||||
| 			Storage: storage, | ||||
| 		} | ||||
| 		resp, err = b.HandleRequest(ctx, loginReq) | ||||
| 		if err != nil || (resp != nil && resp.IsError()) { | ||||
| 			t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 		} | ||||
| 		expected := []string{"grouppolicy", "userpolicy"} | ||||
| 		if !reflect.DeepEqual(expected, resp.Auth.Policies) { | ||||
| 			t.Fatalf("bad: policies: expected: %q, actual: %q", expected, resp.Auth.Policies) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	configReq := &logical.Request{ | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      "config", | ||||
| 		Data: map[string]interface{}{ | ||||
| 			// Online LDAP test server | ||||
| 			// http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ | ||||
| 			"url":      "ldap://ldap.forumsys.com", | ||||
| 			"userattr": "uid", | ||||
| 			"userdn":   "dc=example,dc=com", | ||||
| 			"groupdn":  "dc=example,dc=com", | ||||
| 			"binddn":   "cn=read-only-admin,dc=example,dc=com", | ||||
| 		}, | ||||
| 		Storage: storage, | ||||
| 	} | ||||
| 	resp, err = b.HandleRequest(ctx, configReq) | ||||
| 	if err != nil || (resp != nil && resp.IsError()) { | ||||
| 		t.Fatalf("err:%v resp:%#v", err, resp) | ||||
| 	} | ||||
|  | ||||
| 	testVals(false) | ||||
|  | ||||
| 	// Check that if the value is nil, on read it is case sensitive | ||||
| 	configEntry, err := b.Config(ctx, configReq) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	configEntry.CaseSensitiveNames = nil | ||||
| 	entry, err := logical.StorageEntryJSON("config", configEntry) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	err = configReq.Storage.Put(ctx, entry) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	testVals(true) | ||||
| } | ||||
|  | ||||
| func TestLdapAuthBackend_UserPolicies(t *testing.T) { | ||||
| 	var resp *logical.Response | ||||
| 	var err error | ||||
| @@ -282,6 +458,7 @@ func testAccStepConfigUrl(t *testing.T) logicaltest.TestStep { | ||||
| 			"userattr":             "uid", | ||||
| 			"userdn":               "dc=example,dc=com", | ||||
| 			"groupdn":              "dc=example,dc=com", | ||||
| 			"case_sensitive_names": true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| @@ -300,6 +477,7 @@ func testAccStepConfigUrlWithAuthBind(t *testing.T) logicaltest.TestStep { | ||||
| 			"groupdn":              "dc=example,dc=com", | ||||
| 			"binddn":               "cn=read-only-admin,dc=example,dc=com", | ||||
| 			"bindpass":             "password", | ||||
| 			"case_sensitive_names": true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| @@ -316,6 +494,7 @@ func testAccStepConfigUrlWithDiscover(t *testing.T) logicaltest.TestStep { | ||||
| 			"userdn":               "dc=example,dc=com", | ||||
| 			"groupdn":              "dc=example,dc=com", | ||||
| 			"discoverdn":           true, | ||||
| 			"case_sensitive_names": true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| @@ -331,6 +510,7 @@ func testAccStepConfigUrlNoGroupDN(t *testing.T) logicaltest.TestStep { | ||||
| 			"userattr":             "uid", | ||||
| 			"userdn":               "dc=example,dc=com", | ||||
| 			"discoverdn":           true, | ||||
| 			"case_sensitive_names": true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import ( | ||||
| 	"github.com/go-ldap/ldap" | ||||
| 	log "github.com/hashicorp/go-hclog" | ||||
| 	multierror "github.com/hashicorp/go-multierror" | ||||
| 	"github.com/hashicorp/vault/helper/consts" | ||||
| 	"github.com/hashicorp/vault/helper/tlsutil" | ||||
| 	"github.com/hashicorp/vault/logical" | ||||
| 	"github.com/hashicorp/vault/logical/framework" | ||||
| @@ -109,11 +110,17 @@ Default: cn`, | ||||
| 				Default:     "tls12", | ||||
| 				Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'", | ||||
| 			}, | ||||
|  | ||||
| 			"deny_null_bind": &framework.FieldSchema{ | ||||
| 				Type:        framework.TypeBool, | ||||
| 				Default:     true, | ||||
| 				Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true", | ||||
| 			}, | ||||
|  | ||||
| 			"case_sensitive_names": &framework.FieldSchema{ | ||||
| 				Type:        framework.TypeBool, | ||||
| 				Description: "If true, case sensitivity will be used when comparing usernames and groups for matching policies.", | ||||
| 			}, | ||||
| 		}, | ||||
|  | ||||
| 		Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| @@ -149,6 +156,9 @@ func (b *backend) Config(ctx context.Context, req *logical.Request) (*ConfigEntr | ||||
|  | ||||
| 	if storedConfig == nil { | ||||
| 		// No user overrides, return default configuration | ||||
| 		result.CaseSensitiveNames = new(bool) | ||||
| 		*result.CaseSensitiveNames = false | ||||
|  | ||||
| 		return result, nil | ||||
| 	} | ||||
|  | ||||
| @@ -158,6 +168,24 @@ func (b *backend) Config(ctx context.Context, req *logical.Request) (*ConfigEntr | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var persistNeeded bool | ||||
| 	if result.CaseSensitiveNames == nil { | ||||
| 		// Upgrade from before switching to case-insensitive | ||||
| 		result.CaseSensitiveNames = new(bool) | ||||
| 		*result.CaseSensitiveNames = true | ||||
| 		persistNeeded = true | ||||
| 	} | ||||
|  | ||||
| 	if persistNeeded && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) { | ||||
| 		entry, err := logical.StorageEntryJSON("config", result) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if err := req.Storage.Put(ctx, entry); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	result.logger = b.Logger() | ||||
|  | ||||
| 	return result, nil | ||||
| @@ -189,6 +217,7 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f | ||||
| 			"discoverdn":           cfg.DiscoverDN, | ||||
| 			"tls_min_version":      cfg.TLSMinVersion, | ||||
| 			"tls_max_version":      cfg.TLSMaxVersion, | ||||
| 			"case_sensitive_names": *cfg.CaseSensitiveNames, | ||||
| 		}, | ||||
| 	} | ||||
| 	return resp, nil | ||||
| @@ -282,23 +311,33 @@ func (b *backend) newConfigEntry(d *framework.FieldData) (*ConfigEntry, error) { | ||||
| 	if startTLS { | ||||
| 		cfg.StartTLS = startTLS | ||||
| 	} | ||||
|  | ||||
| 	bindDN := d.Get("binddn").(string) | ||||
| 	if bindDN != "" { | ||||
| 		cfg.BindDN = bindDN | ||||
| 	} | ||||
|  | ||||
| 	bindPass := d.Get("bindpass").(string) | ||||
| 	if bindPass != "" { | ||||
| 		cfg.BindPassword = bindPass | ||||
| 	} | ||||
|  | ||||
| 	denyNullBind := d.Get("deny_null_bind").(bool) | ||||
| 	if denyNullBind { | ||||
| 		cfg.DenyNullBind = denyNullBind | ||||
| 	} | ||||
|  | ||||
| 	discoverDN := d.Get("discoverdn").(bool) | ||||
| 	if discoverDN { | ||||
| 		cfg.DiscoverDN = discoverDN | ||||
| 	} | ||||
|  | ||||
| 	caseSensitiveNames, ok := d.GetOk("case_sensitive_names") | ||||
| 	if ok { | ||||
| 		cfg.CaseSensitiveNames = new(bool) | ||||
| 		*cfg.CaseSensitiveNames = caseSensitiveNames.(bool) | ||||
| 	} | ||||
|  | ||||
| 	return cfg, nil | ||||
| } | ||||
|  | ||||
| @@ -309,6 +348,13 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * | ||||
| 		return logical.ErrorResponse(err.Error()), nil | ||||
| 	} | ||||
|  | ||||
| 	// On write, if not specified, use false. We do this here so upgrade logic | ||||
| 	// works since it calls the same newConfigEntry function | ||||
| 	if cfg.CaseSensitiveNames == nil { | ||||
| 		cfg.CaseSensitiveNames = new(bool) | ||||
| 		*cfg.CaseSensitiveNames = false | ||||
| 	} | ||||
|  | ||||
| 	entry, err := logical.StorageEntryJSON("config", cfg) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -322,22 +368,23 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * | ||||
|  | ||||
| type ConfigEntry struct { | ||||
| 	logger             log.Logger | ||||
| 	Url           string `json:"url" structs:"url" mapstructure:"url"` | ||||
| 	UserDN        string `json:"userdn" structs:"userdn" mapstructure:"userdn"` | ||||
| 	GroupDN       string `json:"groupdn" structs:"groupdn" mapstructure:"groupdn"` | ||||
| 	GroupFilter   string `json:"groupfilter" structs:"groupfilter" mapstructure:"groupfilter"` | ||||
| 	GroupAttr     string `json:"groupattr" structs:"groupattr" mapstructure:"groupattr"` | ||||
| 	UPNDomain     string `json:"upndomain" structs:"upndomain" mapstructure:"upndomain"` | ||||
| 	UserAttr      string `json:"userattr" structs:"userattr" mapstructure:"userattr"` | ||||
| 	Certificate   string `json:"certificate" structs:"certificate" mapstructure:"certificate"` | ||||
| 	InsecureTLS   bool   `json:"insecure_tls" structs:"insecure_tls" mapstructure:"insecure_tls"` | ||||
| 	StartTLS      bool   `json:"starttls" structs:"starttls" mapstructure:"starttls"` | ||||
| 	BindDN        string `json:"binddn" structs:"binddn" mapstructure:"binddn"` | ||||
| 	BindPassword  string `json:"bindpass" structs:"bindpass" mapstructure:"bindpass"` | ||||
| 	DenyNullBind  bool   `json:"deny_null_bind" structs:"deny_null_bind" mapstructure:"deny_null_bind"` | ||||
| 	DiscoverDN    bool   `json:"discoverdn" structs:"discoverdn" mapstructure:"discoverdn"` | ||||
| 	TLSMinVersion string `json:"tls_min_version" structs:"tls_min_version" mapstructure:"tls_min_version"` | ||||
| 	TLSMaxVersion string `json:"tls_max_version" structs:"tls_max_version" mapstructure:"tls_max_version"` | ||||
| 	Url                string `json:"url"` | ||||
| 	UserDN             string `json:"userdn"` | ||||
| 	GroupDN            string `json:"groupdn"` | ||||
| 	GroupFilter        string `json:"groupfilter"` | ||||
| 	GroupAttr          string `json:"groupattr"` | ||||
| 	UPNDomain          string `json:"upndomain"` | ||||
| 	UserAttr           string `json:"userattr"` | ||||
| 	Certificate        string `json:"certificate"` | ||||
| 	InsecureTLS        bool   `json:"insecure_tls"` | ||||
| 	StartTLS           bool   `json:"starttls"` | ||||
| 	BindDN             string `json:"binddn"` | ||||
| 	BindPassword       string `json:"bindpass"` | ||||
| 	DenyNullBind       bool   `json:"deny_null_bind"` | ||||
| 	DiscoverDN         bool   `json:"discoverdn"` | ||||
| 	TLSMinVersion      string `json:"tls_min_version"` | ||||
| 	TLSMaxVersion      string `json:"tls_max_version"` | ||||
| 	CaseSensitiveNames *bool  `json:"case_sensitive_names,omitempty` | ||||
| } | ||||
|  | ||||
| func (c *ConfigEntry) GetTLSConfig(host string) (*tls.Config, error) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package ldap | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/helper/policyutil" | ||||
| 	"github.com/hashicorp/vault/logical" | ||||
| @@ -74,7 +75,20 @@ func (b *backend) pathGroupDelete(ctx context.Context, req *logical.Request, d * | ||||
| } | ||||
|  | ||||
| func (b *backend) pathGroupRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	group, err := b.Group(ctx, req.Storage, d.Get("name").(string)) | ||||
| 	groupname := d.Get("name").(string) | ||||
|  | ||||
| 	cfg, err := b.Config(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if cfg == nil { | ||||
| 		return logical.ErrorResponse("ldap backend not configured"), nil | ||||
| 	} | ||||
| 	if !*cfg.CaseSensitiveNames { | ||||
| 		groupname = strings.ToLower(groupname) | ||||
| 	} | ||||
|  | ||||
| 	group, err := b.Group(ctx, req.Storage, groupname) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -90,8 +104,21 @@ func (b *backend) pathGroupRead(ctx context.Context, req *logical.Request, d *fr | ||||
| } | ||||
|  | ||||
| func (b *backend) pathGroupWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	groupname := d.Get("name").(string) | ||||
|  | ||||
| 	cfg, err := b.Config(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if cfg == nil { | ||||
| 		return logical.ErrorResponse("ldap backend not configured"), nil | ||||
| 	} | ||||
| 	if !*cfg.CaseSensitiveNames { | ||||
| 		groupname = strings.ToLower(groupname) | ||||
| 	} | ||||
|  | ||||
| 	// Store it | ||||
| 	entry, err := logical.StorageEntryJSON("group/"+d.Get("name").(string), &GroupEntry{ | ||||
| 	entry, err := logical.StorageEntryJSON("group/"+groupname, &GroupEntry{ | ||||
| 		Policies: policyutil.ParsePolicies(d.Get("policies")), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -81,7 +81,20 @@ func (b *backend) pathUserDelete(ctx context.Context, req *logical.Request, d *f | ||||
| } | ||||
|  | ||||
| func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	user, err := b.User(ctx, req.Storage, d.Get("name").(string)) | ||||
| 	username := d.Get("name").(string) | ||||
|  | ||||
| 	cfg, err := b.Config(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if cfg == nil { | ||||
| 		return logical.ErrorResponse("ldap backend not configured"), nil | ||||
| 	} | ||||
| 	if !*cfg.CaseSensitiveNames { | ||||
| 		username = strings.ToLower(username) | ||||
| 	} | ||||
|  | ||||
| 	user, err := b.User(ctx, req.Storage, username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -98,15 +111,29 @@ func (b *backend) pathUserRead(ctx context.Context, req *logical.Request, d *fra | ||||
| } | ||||
|  | ||||
| func (b *backend) pathUserWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	name := d.Get("name").(string) | ||||
| 	groups := strutil.RemoveDuplicates(strutil.ParseStringSlice(d.Get("groups").(string), ","), false) | ||||
| 	lowercaseGroups := false | ||||
| 	username := d.Get("name").(string) | ||||
|  | ||||
| 	cfg, err := b.Config(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if cfg == nil { | ||||
| 		return logical.ErrorResponse("ldap backend not configured"), nil | ||||
| 	} | ||||
| 	if !*cfg.CaseSensitiveNames { | ||||
| 		username = strings.ToLower(username) | ||||
| 		lowercaseGroups = true | ||||
| 	} | ||||
|  | ||||
| 	groups := strutil.RemoveDuplicates(strutil.ParseStringSlice(d.Get("groups").(string), ","), lowercaseGroups) | ||||
| 	policies := policyutil.ParsePolicies(d.Get("policies")) | ||||
| 	for i, g := range groups { | ||||
| 		groups[i] = strings.TrimSpace(g) | ||||
| 	} | ||||
|  | ||||
| 	// Store it | ||||
| 	entry, err := logical.StorageEntryJSON("user/"+name, &UserEntry{ | ||||
| 	entry, err := logical.StorageEntryJSON("user/"+username, &UserEntry{ | ||||
| 		Groups:   groups, | ||||
| 		Policies: policies, | ||||
| 	}) | ||||
|   | ||||
| @@ -28,6 +28,11 @@ This endpoint configures the LDAP auth method. | ||||
|  | ||||
| - `url` `(string: <required>)` – The LDAP server to connect to. Examples: | ||||
|   `ldap://ldap.myorg.com`, `ldaps://ldap.myorg.com:636` | ||||
| - `case_sensitive_names` `(bool: false)` – If set, user and group names | ||||
|   assigned to policies within the backend will be case sensitive. Otherwise, | ||||
|   names will be normalized to lower case. Case will still be preserved when | ||||
|   sending the username to the LDAP server at login time; this is only for | ||||
|   matching local user/group definitions. | ||||
| - `starttls` `(bool: false)` – If true, issues a `StartTLS` command after | ||||
|   establishing an unencrypted connection. | ||||
| - `tls_min_version` `(string: tls12)` – Minimum TLS version to use. Accepted | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jeff Mitchell
					Jeff Mitchell