mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
Merge branch 'master' into feature/static_password_env
This commit is contained in:
@@ -69,8 +69,8 @@ jobs:
|
||||
DEX_KEYSTONE_ADMIN_USER: demo
|
||||
DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS
|
||||
|
||||
- name: Run Kubernetes tests
|
||||
run: ./scripts/test-k8s.sh
|
||||
- name: Run linter
|
||||
run: make lint
|
||||
|
||||
# Ensure proto generation doesn't depend on external packages.
|
||||
- name: Verify proto
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0.1
|
||||
goimports:
|
||||
local-prefixes: github.com/dexidp/dex
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- funlen
|
||||
- maligned
|
||||
- wsl
|
||||
|
||||
# TODO: fix me
|
||||
- unparam
|
||||
- golint
|
||||
- goconst
|
||||
- staticcheck
|
||||
- nakedret
|
||||
- errcheck
|
||||
- gosec
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- prealloc
|
||||
- scopelint
|
||||
- lll
|
||||
- dupl
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- godox
|
||||
@@ -48,7 +48,6 @@ install:
|
||||
|
||||
script:
|
||||
- make testall
|
||||
- ./scripts/test-k8s.sh
|
||||
- make verify-proto # Ensure proto generation doesn't depend on external packages.
|
||||
|
||||
notifications:
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Authentication using OpenShift
|
||||
|
||||
## Overview
|
||||
|
||||
Dex can make use of users and groups defined within OpenShift by querying the platform provided OAuth server.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
### Creating an OAuth Client
|
||||
|
||||
Two forms of OAuth Clients can be utilized:
|
||||
|
||||
* [Using a Service Account as an OAuth Client](https://docs.openshift.com/container-platform/latest/authentication/using-service-accounts-as-oauth-client.html) (Recommended)
|
||||
* [Registering An Additional OAuth Client](https://docs.openshift.com/container-platform/latest/authentication/configuring-internal-oauth.html#oauth-register-additional-client_configuring-internal-oauth)
|
||||
|
||||
#### Using a Service Account as an OAuth Client
|
||||
|
||||
OpenShift Service Accounts can be used as a constrained form of OAuth client. Making use of a Service Account to represent an OAuth Client is the recommended option as it does not require elevated privileged within the OpenShift cluster. Create a new Service Account or make use of an existing Service Account.
|
||||
|
||||
Patch the Service Account to add an annotation for location of the Redirect URI
|
||||
|
||||
```
|
||||
oc patch serviceaccount <name> --type='json' -p='[{"op": "add", "path": "/metadata/annotations/serviceaccounts.openshift.io~1oauth-redirecturi.dex", "value":"https:///<dex_url>/callback"}]'
|
||||
```
|
||||
|
||||
The Client ID for a Service Account representing an OAuth Client takes the form `
|
||||
|
||||
The Client Secret for a Service Account representing an OAuth Client is the long lived OAuth Token that is configued for the Service Account. Execute the following command to retrieve the OAuth Token.
|
||||
|
||||
```
|
||||
oc serviceaccounts get-token <name>
|
||||
```
|
||||
|
||||
#### Registering An Additional OAuth Client
|
||||
|
||||
Instead of using a constrained form of Service Account to represent an OAuth Client, an additional OAuthClient resource can be created.
|
||||
|
||||
Create a new OAuthClient resource similar to the following:
|
||||
|
||||
```yaml
|
||||
kind: OAuthClient
|
||||
apiVersion: oauth.openshift.io/v1
|
||||
metadata:
|
||||
name: dex
|
||||
# The value that should be utilized as the `client_secret`
|
||||
secret: "<clientSecret>"
|
||||
# List of valid addresses for the callback. Ensure one of the values that are provided is `(dex issuer)/callback`
|
||||
redirectURIs:
|
||||
- "https:///<dex_url>/callback"
|
||||
grantMethod: prompt
|
||||
```
|
||||
|
||||
### Dex Configuration
|
||||
|
||||
The following is an example of a configuration for `examples/config-dev.yaml`:
|
||||
|
||||
```yaml
|
||||
connectors:
|
||||
- type: openshift
|
||||
# Required field for connector id.
|
||||
id: openshift
|
||||
# Required field for connector name.
|
||||
name: OpenShift
|
||||
config:
|
||||
# OpenShift API
|
||||
issuer: https://api.mycluster.example.com:6443
|
||||
# Credentials can be string literals or pulled from the environment.
|
||||
clientID: $OPENSHIFT_OAUTH_CLIENT_ID
|
||||
clientSecret: $OPENSHIFT_OAUTH_CLIENT_SECRET
|
||||
redirectURI: http://127.0.0.1:5556/dex/
|
||||
# Optional: Specify whether to communicate to OpenShift without validating SSL ceertificates
|
||||
insecureCA: false
|
||||
# Optional: The location of file containing SSL certificates to commmunicate to OpenShift
|
||||
rootCA: /etc/ssl/openshift.pem
|
||||
# Optional list of required groups a user mmust be a member of
|
||||
groups:
|
||||
- users
|
||||
```
|
||||
@@ -1,21 +1,5 @@
|
||||
# Running integration tests
|
||||
|
||||
## Kubernetes
|
||||
|
||||
Kubernetes tests run against a Kubernetes API server, and are enabled by the `DEX_KUBECONFIG` environment variable:
|
||||
|
||||
```
|
||||
$ export DEX_KUBECONFIG=~/.kube/config
|
||||
$ go test -v -i ./storage/kubernetes
|
||||
$ go test -v ./storage/kubernetes
|
||||
```
|
||||
|
||||
These tests can be executed locally using docker by running the following script:
|
||||
|
||||
```
|
||||
$ ./scripts/test-k8s.sh
|
||||
```
|
||||
|
||||
## Postgres
|
||||
|
||||
Running database tests locally requires:
|
||||
|
||||
@@ -14,11 +14,13 @@ Steps:
|
||||
```yaml
|
||||
frontend:
|
||||
dir: /path/to/custom/web
|
||||
issuer: my-dex
|
||||
extra:
|
||||
tos_footer_link: "https://example.com/terms"
|
||||
client_logo_url: "../theme/client-logo.png"
|
||||
foo: "bar"
|
||||
```
|
||||
5. Set the `frontend.dir` value to your own `web` directory.
|
||||
6. Write the issuer in the `issuer` directory in order to modify the Dex title and the `Log in to <<dex>>` tag.
|
||||
|
||||
To test your templates simply run Dex with a valid configuration and go through a login flow.
|
||||
@@ -2,7 +2,6 @@ PROJ=dex
|
||||
ORG_PATH=github.com/dexidp
|
||||
REPO_PATH=$(ORG_PATH)/$(PROJ)
|
||||
export PATH := $(PWD)/bin:$(PATH)
|
||||
THIS_DIRECTORY:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
VERSION ?= $(shell ./scripts/git-version)
|
||||
|
||||
@@ -18,6 +17,9 @@ export GOBIN=$(PWD)/bin
|
||||
|
||||
LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)"
|
||||
|
||||
# Dependency versions
|
||||
GOLANGCI_VERSION = 1.21.0
|
||||
|
||||
build: bin/dex bin/example-app bin/grpc-client
|
||||
|
||||
bin/dex:
|
||||
@@ -39,20 +41,38 @@ revendor:
|
||||
@go mod vendor -v
|
||||
@go mod verify
|
||||
|
||||
test:
|
||||
test: bin/test/kube-apiserver bin/test/etcd
|
||||
@go test -v ./...
|
||||
|
||||
testrace:
|
||||
testrace: bin/test/kube-apiserver bin/test/etcd
|
||||
@go test -v --race ./...
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
export TEST_ASSET_KUBE_APISERVER=$(abspath bin/test/kube-apiserver)
|
||||
export TEST_ASSET_ETCD=$(abspath bin/test/etcd)
|
||||
|
||||
fmt:
|
||||
@./scripts/gofmt ./...
|
||||
bin/test/kube-apiserver:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/kube-apiserver-$(shell uname)-x86_64 > bin/test/kube-apiserver
|
||||
chmod +x bin/test/kube-apiserver
|
||||
|
||||
lint: bin/golint
|
||||
@./bin/golint -set_exit_status $(shell go list ./...)
|
||||
bin/test/etcd:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/etcd-$(shell uname)-x86_64 > bin/test/etcd
|
||||
chmod +x bin/test/etcd
|
||||
|
||||
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||
@mv bin/golangci-lint $@
|
||||
|
||||
.PHONY: lint
|
||||
lint: bin/golangci-lint ## Run linter
|
||||
bin/golangci-lint run
|
||||
|
||||
.PHONY: fix
|
||||
fix: bin/golangci-lint ## Fix lint violations
|
||||
bin/golangci-lint run --fix
|
||||
|
||||
.PHONY: docker-image
|
||||
docker-image:
|
||||
@@ -73,14 +93,11 @@ bin/protoc: scripts/get-protoc
|
||||
bin/protoc-gen-go:
|
||||
@go install -v $(REPO_PATH)/vendor/github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
bin/golint:
|
||||
@go install -v $(THIS_DIRECTORY)/vendor/golang.org/x/lint/golint
|
||||
|
||||
clean:
|
||||
@rm -rf bin/
|
||||
|
||||
testall: testrace vet fmt lint
|
||||
testall: testrace
|
||||
|
||||
FORCE:
|
||||
|
||||
.PHONY: test testrace vet fmt lint testall
|
||||
.PHONY: test testrace testall
|
||||
|
||||
@@ -74,6 +74,7 @@ Dex implements the following connectors:
|
||||
| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | no | beta | |
|
||||
| [AuthProxy](Documentation/connectors/authproxy.md) | no | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
|
||||
| [Bitbucket Cloud](Documentation/connectors/bitbucketcloud.md) | yes | yes | no | alpha | |
|
||||
| [OpenShift](Documentation/connectors/openshift.md) | no | yes | no | stable | |
|
||||
|
||||
Stable, beta, and alpha are defined as:
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/dexidp/dex/connector/mock"
|
||||
"github.com/dexidp/dex/connector/oidc"
|
||||
"github.com/dexidp/dex/server"
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/storage/sql"
|
||||
)
|
||||
@@ -215,7 +216,6 @@ logger:
|
||||
if diff := pretty.Compare(c, want); diff != "" {
|
||||
t.Errorf("got!=want: %s", diff)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUnmarshalConfigWithEnv(t *testing.T) {
|
||||
|
||||
@@ -182,7 +182,6 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("failed to initialize storage connectors: %v", err)
|
||||
}
|
||||
storageConnectors[i] = conn
|
||||
|
||||
}
|
||||
|
||||
if c.EnablePasswordDB {
|
||||
|
||||
@@ -143,7 +143,7 @@ func cmd() *cobra.Command {
|
||||
ctx := oidc.ClientContext(context.Background(), a.client)
|
||||
provider, err := oidc.NewProvider(ctx, issuerURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to query provider %q: %v", issuerURL, err)
|
||||
return fmt.Errorf("failed to query provider %q: %v", issuerURL, err)
|
||||
}
|
||||
|
||||
var s struct {
|
||||
@@ -153,7 +153,7 @@ func cmd() *cobra.Command {
|
||||
ScopesSupported []string `json:"scopes_supported"`
|
||||
}
|
||||
if err := provider.Claims(&s); err != nil {
|
||||
return fmt.Errorf("Failed to parse provider scopes_supported: %v", err)
|
||||
return fmt.Errorf("failed to parse provider scopes_supported: %v", err)
|
||||
}
|
||||
|
||||
if len(s.ScopesSupported) == 0 {
|
||||
|
||||
@@ -41,7 +41,6 @@ type Config struct {
|
||||
|
||||
// Open returns a strategy for logging in through Bitbucket.
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
|
||||
b := bitbucketConnector{
|
||||
redirectURI: c.RedirectURI,
|
||||
teams: c.Teams,
|
||||
@@ -373,7 +372,6 @@ type userTeamsResponse struct {
|
||||
}
|
||||
|
||||
func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
|
||||
var teams []string
|
||||
apiURL := b.apiURL + "/teams?role=member"
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func TestUserGroups(t *testing.T) {
|
||||
|
||||
teamsResponse := userTeamsResponse{
|
||||
pagedResponse: pagedResponse{
|
||||
Size: 3,
|
||||
@@ -46,7 +45,6 @@ func TestUserGroups(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserWithoutTeams(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/teams?role=member": userTeamsResponse{},
|
||||
})
|
||||
@@ -61,7 +59,6 @@ func TestUserWithoutTeams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/user": user{Username: "some-login"},
|
||||
"/user/emails": userEmailResponse{
|
||||
|
||||
@@ -67,7 +67,6 @@ type Org struct {
|
||||
|
||||
// Open returns a strategy for logging in through GitHub.
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
|
||||
if c.Org != "" {
|
||||
// Return error if both 'org' and 'orgs' fields are used.
|
||||
if len(c.Orgs) > 0 {
|
||||
@@ -107,7 +106,6 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
|
||||
if g.httpClient, err = newHTTPClient(g.rootCA); err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP client: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
g.loadAllGroups = c.LoadAllGroups
|
||||
|
||||
@@ -144,7 +142,7 @@ type githubConnector struct {
|
||||
hostName string
|
||||
// Used to support untrusted/self-signed CA certs.
|
||||
rootCA string
|
||||
// HTTP Client that trusts the custom delcared rootCA cert.
|
||||
// HTTP Client that trusts the custom declared rootCA cert.
|
||||
httpClient *http.Client
|
||||
// optional choice between 'name' (default) or 'slug'
|
||||
teamNameField string
|
||||
@@ -206,7 +204,7 @@ func (e *oauth2Error) Error() string {
|
||||
return e.error + ": " + e.errorDescription
|
||||
}
|
||||
|
||||
// newHTTPClient returns a new HTTP client that trusts the custom delcared rootCA cert.
|
||||
// newHTTPClient returns a new HTTP client that trusts the custom declared rootCA cert.
|
||||
func newHTTPClient(rootCA string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCA)
|
||||
|
||||
@@ -126,7 +126,6 @@ func TestUserGroupsWithTeamNameAndSlugFieldConfig(t *testing.T) {
|
||||
|
||||
// tests that the users login is used as their username when they have no username set
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]testResponse{
|
||||
"/user": {data: user{Login: "some-login", ID: 12345678}},
|
||||
"/user/emails": {data: []userEmail{{
|
||||
@@ -168,7 +167,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]testResponse{
|
||||
"/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}},
|
||||
"/user/emails": {data: []userEmail{{
|
||||
|
||||
@@ -65,7 +65,6 @@ func TestUserGroupsWithoutOrgs(t *testing.T) {
|
||||
|
||||
// tests that the email is used as their username when they have no username set
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
@@ -102,7 +101,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
@@ -130,7 +128,6 @@ func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginWithTeamWhitelisted(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
@@ -158,7 +155,6 @@ func TestLoginWithTeamWhitelisted(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginWithTeamNonWhitelisted(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
admin "google.golang.org/api/admin/directory/v1"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
pkg_groups "github.com/dexidp/dex/pkg/groups"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
"golang.org/x/oauth2/google"
|
||||
admin "google.golang.org/api/admin/directory/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -105,7 +105,6 @@ type googleConnector struct {
|
||||
redirectURI string
|
||||
oauth2Config *oauth2.Config
|
||||
verifier *oidc.IDTokenVerifier
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
hostedDomains []string
|
||||
|
||||
@@ -150,7 +150,6 @@ func (p *conn) Prompt() string { return "username" }
|
||||
|
||||
func (p *conn) Refresh(
|
||||
ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
|
||||
|
||||
token, err := p.getAdminToken(ctx)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err)
|
||||
@@ -210,6 +209,8 @@ func (p *conn) getAdminToken(ctx context.Context) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
token := resp.Header.Get("X-Subject-Token")
|
||||
return token, nil
|
||||
}
|
||||
@@ -229,6 +230,7 @@ func (p *conn) checkIfUserExists(ctx context.Context, userID string, token strin
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return true, nil
|
||||
|
||||
@@ -154,7 +154,12 @@ func delete(t *testing.T, token, id, uri string) {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Auth-Token", token)
|
||||
client.Do(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
func createGroup(t *testing.T, token, description, name string) string {
|
||||
@@ -208,7 +213,13 @@ func addUserToGroup(t *testing.T, token, groupID, userID string) error {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Auth-Token", token)
|
||||
client.Do(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -274,7 +285,7 @@ func TestUseRefreshToken(t *testing.T) {
|
||||
delete(t, token, groupID, groupsURL)
|
||||
|
||||
expectEquals(t, 1, len(identityRefresh.Groups))
|
||||
expectEquals(t, testGroup, string(identityRefresh.Groups[0]))
|
||||
expectEquals(t, testGroup, identityRefresh.Groups[0])
|
||||
}
|
||||
|
||||
func TestUseRefreshTokenUserDeleted(t *testing.T) {
|
||||
|
||||
@@ -189,7 +189,6 @@ func (c *Config) OpenConnector(logger log.Logger) (interface {
|
||||
}
|
||||
|
||||
func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
|
||||
|
||||
requiredFields := []struct {
|
||||
name string
|
||||
val string
|
||||
@@ -365,7 +364,6 @@ func (c *ldapConnector) identityFromEntry(user ldap.Entry) (ident connector.Iden
|
||||
}
|
||||
|
||||
func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.Entry, found bool, err error) {
|
||||
|
||||
filter := fmt.Sprintf("(%s=%s)", c.UserSearch.Username, ldap.EscapeFilter(username))
|
||||
if c.UserSearch.Filter != "" {
|
||||
filter = fmt.Sprintf("(&%s%s)", c.UserSearch.Filter, filter)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user