Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
75a07f2b
Unverified
Commit
75a07f2b
authored
Nov 10, 2017
by
Eric Chiang
Committed by
GitHub
Nov 10, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1116 from srenatus/sr/local-users/say-email-in-login
password connectors: make prompt configurable
parents
04e276f2
b09a1345
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
76 additions
and
7 deletions
+76
-7
ldap-connector.md
Documentation/ldap-connector.md
+4
-0
connector.go
connector/connector.go
+3
-0
ldap.go
connector/ldap/ldap.go
+9
-0
ldap_test.go
connector/ldap/ldap_test.go
+25
-0
connectortest.go
connector/mock/connectortest.go
+2
-0
config-ldap.yaml
examples/config-ldap.yaml
+2
-0
handlers.go
server/handlers.go
+10
-2
server.go
server/server.go
+4
-0
server_test.go
server/server_test.go
+10
-0
templates.go
server/templates.go
+4
-2
password.html
web/templates/password.html
+3
-3
No files found.
Documentation/ldap-connector.md
View file @
75a07f2b
...
...
@@ -90,6 +90,10 @@ connectors:
bindDN
:
uid=seviceaccount,cn=users,dc=example,dc=com
bindPW
:
password
# The attribute to display in the provided password prompt. If unset, will
# display "Username"
usernamePrompt
:
SSO Username
# User search maps a username and password entered by a user to a LDAP entry.
userSearch
:
# BaseDN to start the search from. It will translate to the query
...
...
connector/connector.go
View file @
75a07f2b
...
...
@@ -39,7 +39,10 @@ type Identity struct {
// PasswordConnector is an interface implemented by connectors which take a
// username and password.
// Prompt() is used to inform the handler what to display in the password
// template. If this returns an empty string, it'll default to "Username".
type
PasswordConnector
interface
{
Prompt
()
string
Login
(
ctx
context
.
Context
,
s
Scopes
,
username
,
password
string
)
(
identity
Identity
,
validPassword
bool
,
err
error
)
}
...
...
connector/ldap/ldap.go
View file @
75a07f2b
...
...
@@ -77,6 +77,11 @@ type Config struct {
BindDN
string
`json:"bindDN"`
BindPW
string
`json:"bindPW"`
// UsernamePrompt allows users to override the username attribute (displayed
// in the username/password prompt). If unset, the handler will use
// "Username".
UsernamePrompt
string
`json:"usernamePrompt"`
// User entry search configuration.
UserSearch
struct
{
// BsaeDN to start the search from. For example "cn=users,dc=example,dc=com"
...
...
@@ -545,3 +550,7 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string,
}
return
groupNames
,
nil
}
func
(
c
*
ldapConnector
)
Prompt
()
string
{
return
c
.
UsernamePrompt
}
connector/ldap/ldap_test.go
View file @
75a07f2b
...
...
@@ -437,6 +437,31 @@ userpassword: foo
runTests
(
t
,
schema
,
connectLDAPS
,
c
,
tests
)
}
func
TestUsernamePrompt
(
t
*
testing
.
T
)
{
tests
:=
map
[
string
]
struct
{
config
Config
expected
string
}{
"with usernamePrompt unset it returns
\"\"
"
:
{
config
:
Config
{},
expected
:
""
,
},
"with usernamePrompt set it returns that"
:
{
config
:
Config
{
UsernamePrompt
:
"Email address"
},
expected
:
"Email address"
,
},
}
for
n
,
d
:=
range
tests
{
t
.
Run
(
n
,
func
(
t
*
testing
.
T
)
{
conn
:=
&
ldapConnector
{
Config
:
d
.
config
}
if
actual
:=
conn
.
Prompt
();
actual
!=
d
.
expected
{
t
.
Errorf
(
"expected %v, got %v"
,
d
.
expected
,
actual
)
}
})
}
}
// runTests runs a set of tests against an LDAP schema. It does this by
// setting up an OpenLDAP server and injecting the provided scheme.
//
...
...
connector/mock/connectortest.go
View file @
75a07f2b
...
...
@@ -110,3 +110,5 @@ func (p passwordConnector) Login(ctx context.Context, s connector.Scopes, userna
}
return
identity
,
false
,
nil
}
func
(
p
passwordConnector
)
Prompt
()
string
{
return
""
}
examples/config-ldap.yaml
View file @
75a07f2b
...
...
@@ -20,6 +20,8 @@ connectors:
bindDN
:
cn=admin,dc=example,dc=org
bindPW
:
admin
usernamePrompt
:
Email Address
userSearch
:
baseDN
:
ou=People,dc=example,dc=org
filter
:
"
(objectClass=person)"
...
...
server/handlers.go
View file @
75a07f2b
...
...
@@ -250,7 +250,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
}
http
.
Redirect
(
w
,
r
,
callbackURL
,
http
.
StatusFound
)
case
connector
.
PasswordConnector
:
if
err
:=
s
.
templates
.
password
(
w
,
r
.
URL
.
String
(),
""
,
false
);
err
!=
nil
{
if
err
:=
s
.
templates
.
password
(
w
,
r
.
URL
.
String
(),
""
,
usernamePrompt
(
conn
),
false
);
err
!=
nil
{
s
.
logger
.
Errorf
(
"Server template error: %v"
,
err
)
}
case
connector
.
SAMLConnector
:
...
...
@@ -298,7 +298,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
return
}
if
!
ok
{
if
err
:=
s
.
templates
.
password
(
w
,
r
.
URL
.
String
(),
username
,
true
);
err
!=
nil
{
if
err
:=
s
.
templates
.
password
(
w
,
r
.
URL
.
String
(),
username
,
usernamePrompt
(
passwordConnector
),
true
);
err
!=
nil
{
s
.
logger
.
Errorf
(
"Server template error: %v"
,
err
)
}
return
...
...
@@ -1005,3 +1005,11 @@ func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description s
s
.
logger
.
Errorf
(
"token error response: %v"
,
err
)
}
}
// Check for username prompt override from connector. Defaults to "Username".
func
usernamePrompt
(
conn
connector
.
PasswordConnector
)
string
{
if
attr
:=
conn
.
Prompt
();
attr
!=
""
{
return
attr
}
return
"Username"
}
server/server.go
View file @
75a07f2b
...
...
@@ -344,6 +344,10 @@ func (db passwordDB) Refresh(ctx context.Context, s connector.Scopes, identity c
return
identity
,
nil
}
func
(
db
passwordDB
)
Prompt
()
string
{
return
"Email Address"
}
// newKeyCacher returns a storage which caches keys so long as the next
func
newKeyCacher
(
s
storage
.
Storage
,
now
func
()
time
.
Time
)
storage
.
Storage
{
if
now
==
nil
{
...
...
server/server_test.go
View file @
75a07f2b
...
...
@@ -1017,6 +1017,16 @@ func TestPasswordDB(t *testing.T) {
}
func
TestPasswordDBUsernamePrompt
(
t
*
testing
.
T
)
{
s
:=
memory
.
New
(
logger
)
conn
:=
newPasswordDB
(
s
)
expected
:=
"Email Address"
if
actual
:=
conn
.
Prompt
();
actual
!=
expected
{
t
.
Errorf
(
"expected %v, got %v"
,
expected
,
actual
)
}
}
type
storageWithKeysTrigger
struct
{
storage
.
Storage
f
func
()
...
...
server/templates.go
View file @
75a07f2b
...
...
@@ -139,6 +139,7 @@ func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
"issuer"
:
func
()
string
{
return
c
.
issuer
},
"logo"
:
func
()
string
{
return
c
.
logoURL
},
"url"
:
func
(
s
string
)
string
{
return
join
(
c
.
issuerURL
,
s
)
},
"lower"
:
strings
.
ToLower
,
}
tmpls
,
err
:=
template
.
New
(
""
)
.
Funcs
(
funcs
)
.
ParseFiles
(
filenames
...
)
...
...
@@ -189,12 +190,13 @@ func (t *templates) login(w http.ResponseWriter, connectors []connectorInfo) err
return
renderTemplate
(
w
,
t
.
loginTmpl
,
data
)
}
func
(
t
*
templates
)
password
(
w
http
.
ResponseWriter
,
postURL
,
lastUsername
string
,
lastWasInvalid
bool
)
error
{
func
(
t
*
templates
)
password
(
w
http
.
ResponseWriter
,
postURL
,
lastUsername
,
usernamePrompt
string
,
lastWasInvalid
bool
)
error
{
data
:=
struct
{
PostURL
string
Username
string
UsernamePrompt
string
Invalid
bool
}{
postURL
,
lastUsername
,
lastWasInvalid
}
}{
postURL
,
lastUsername
,
usernamePrompt
,
lastWasInvalid
}
return
renderTemplate
(
w
,
t
.
passwordTmpl
,
data
)
}
...
...
web/templates/password.html
View file @
75a07f2b
...
...
@@ -5,9 +5,9 @@
<form
method=
"post"
action=
"{{ .PostURL }}"
>
<div
class=
"theme-form-row"
>
<div
class=
"theme-form-label"
>
<label
for=
"userid"
>
Username
</label>
<label
for=
"userid"
>
{{ .UsernamePrompt }}
</label>
</div>
<input
tabindex=
"1"
required
id=
"login"
name=
"login"
type=
"text"
class=
"theme-form-input"
placeholder=
"
username
"
{{
if
.
Username
}}
value=
"{{ .Username }}"
{{
else
}}
autofocus
{{
end
}}
/>
<input
tabindex=
"1"
required
id=
"login"
name=
"login"
type=
"text"
class=
"theme-form-input"
placeholder=
"
{{ .UsernamePrompt | lower }}
"
{{
if
.
Username
}}
value=
"{{ .Username }}"
{{
else
}}
autofocus
{{
end
}}
/>
</div>
<div
class=
"theme-form-row"
>
<div
class=
"theme-form-label"
>
...
...
@@ -18,7 +18,7 @@
{{ if .Invalid }}
<div
id=
"login-error"
class=
"dex-error-box"
>
Invalid
username
and password.
Invalid
{{ .UsernamePrompt }}
and password.
</div>
{{ end }}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment