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
608d8ba9
Commit
608d8ba9
authored
Aug 25, 2016
by
Eric Chiang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
*: switch dex to the ported templates
parent
027e3d36
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
207 additions
and
83 deletions
+207
-83
config.go
cmd/dex/config.go
+3
-0
serve.go
cmd/dex/serve.go
+2
-2
handlers.go
server/handlers.go
+8
-7
server.go
server/server.go
+10
-0
server_test.go
server/server_test.go
+7
-7
templates.go
server/templates.go
+162
-67
templates_test.go
server/templates_test.go
+15
-0
No files found.
cmd/dex/config.go
View file @
608d8ba9
...
...
@@ -8,6 +8,7 @@ import (
"github.com/coreos/dex/connector/ldap"
"github.com/coreos/dex/connector/mock"
"github.com/coreos/dex/connector/oidc"
"github.com/coreos/dex/server"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage/kubernetes"
"github.com/coreos/dex/storage/memory"
...
...
@@ -21,6 +22,8 @@ type Config struct {
Web
Web
`yaml:"web"`
OAuth2
OAuth2
`yaml:"oauth2"`
Templates
server
.
TemplateConfig
`yaml:"templates"`
StaticClients
[]
storage
.
Client
`yaml:"staticClients"`
}
...
...
cmd/dex/serve.go
View file @
608d8ba9
...
...
@@ -89,11 +89,11 @@ func serve(cmd *cobra.Command, args []string) error {
}
serverConfig
:=
server
.
Config
{
SupportedResponseTypes
:
c
.
OAuth2
.
ResponseTypes
,
Issuer
:
c
.
Issuer
,
Connectors
:
connectors
,
Storage
:
s
,
SupportedResponseTypes
:
c
.
OAuth2
.
ResponseTypes
,
TemplateConfig
:
c
.
Templates
,
}
serv
,
err
:=
server
.
New
(
serverConfig
)
...
...
server/handlers.go
View file @
608d8ba9
...
...
@@ -129,15 +129,16 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
connectorInfos
:=
make
([]
connectorInfo
,
len
(
s
.
connectors
))
i
:=
0
for
id
:=
range
s
.
connectors
{
for
id
,
conn
:=
range
s
.
connectors
{
connectorInfos
[
i
]
=
connectorInfo
{
DisplayName
:
id
,
ID
:
id
,
Name
:
conn
.
DisplayName
,
URL
:
s
.
absPath
(
"/auth"
,
id
),
}
i
++
}
renderLoginOptions
(
w
,
connectorInfos
,
state
)
s
.
templates
.
login
(
w
,
connectorInfos
,
state
)
}
func
(
s
*
Server
)
handleConnectorLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
...
...
@@ -163,7 +164,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
}
http
.
Redirect
(
w
,
r
,
callbackURL
,
http
.
StatusFound
)
case
connector
.
PasswordConnector
:
renderPasswordTmpl
(
w
,
state
,
r
.
URL
.
String
(),
""
)
s
.
templates
.
password
(
w
,
state
,
r
.
URL
.
String
(),
""
,
false
)
default
:
s
.
notFound
(
w
,
r
)
}
...
...
@@ -174,7 +175,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
return
}
username
:=
r
.
FormValue
(
"
username
"
)
username
:=
r
.
FormValue
(
"
login
"
)
password
:=
r
.
FormValue
(
"password"
)
identity
,
ok
,
err
:=
passwordConnector
.
Login
(
username
,
password
)
...
...
@@ -184,7 +185,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
return
}
if
!
ok
{
renderPasswordTmpl
(
w
,
state
,
r
.
URL
.
String
(),
"Invalid credentials"
)
s
.
templates
.
password
(
w
,
state
,
r
.
URL
.
String
(),
username
,
true
)
return
}
redirectURL
,
err
:=
s
.
finalizeLogin
(
identity
,
state
,
connID
,
conn
.
Connector
)
...
...
@@ -299,7 +300,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
errServerError
,
""
)
return
}
renderApprovalTmpl
(
w
,
authReq
.
ID
,
*
authReq
.
Claims
,
client
,
authReq
.
Scopes
)
s
.
templates
.
approval
(
w
,
authReq
.
ID
,
authReq
.
Claims
.
Username
,
client
.
Name
,
authReq
.
Scopes
)
case
"POST"
:
if
r
.
FormValue
(
"approval"
)
!=
"approve"
{
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
"approval rejected"
,
""
)
...
...
server/server.go
View file @
608d8ba9
...
...
@@ -43,6 +43,8 @@ type Config struct {
// If specified, the server will use this function for determining time.
Now
func
()
time
.
Time
TemplateConfig
TemplateConfig
}
func
value
(
val
,
defaultValue
time
.
Duration
)
time
.
Duration
{
...
...
@@ -63,6 +65,8 @@ type Server struct {
mux
http
.
Handler
templates
*
templates
// If enabled, don't prompt user for approval after logging in through connector.
// No package level API to set this, only used in tests.
skipApproval
bool
...
...
@@ -107,6 +111,11 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
supported
[
respType
]
=
true
}
tmpls
,
err
:=
loadTemplates
(
c
.
TemplateConfig
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"server: failed to load templates: %v"
,
err
)
}
now
:=
c
.
Now
if
now
==
nil
{
now
=
time
.
Now
...
...
@@ -124,6 +133,7 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
supportedResponseTypes
:
supported
,
idTokensValidFor
:
value
(
c
.
IDTokensValidFor
,
24
*
time
.
Hour
),
now
:
now
,
templates
:
tmpls
,
}
for
_
,
conn
:=
range
c
.
Connectors
{
...
...
server/server_test.go
View file @
608d8ba9
...
...
@@ -64,7 +64,7 @@ FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
-----END RSA PRIVATE KEY-----`
)
func
newTestServer
(
updateConfig
func
(
c
*
Config
))
(
*
httptest
.
Server
,
*
Server
)
{
func
newTestServer
(
t
*
testing
.
T
,
updateConfig
func
(
c
*
Config
))
(
*
httptest
.
Server
,
*
Server
)
{
var
server
*
Server
s
:=
httptest
.
NewServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
server
.
ServeHTTP
(
w
,
r
)
...
...
@@ -76,7 +76,7 @@ func newTestServer(updateConfig func(c *Config)) (*httptest.Server, *Server) {
{
ID
:
"mock"
,
DisplayName
:
"Mock"
,
Connector
:
mock
.
New
(),
Connector
:
mock
.
New
CallbackConnector
(),
},
},
}
...
...
@@ -87,21 +87,21 @@ func newTestServer(updateConfig func(c *Config)) (*httptest.Server, *Server) {
var
err
error
if
server
,
err
=
newServer
(
config
,
staticRotationStrategy
(
testKey
));
err
!=
nil
{
panic
(
err
)
t
.
Fatal
(
err
)
}
server
.
skipApproval
=
true
// Don't prompt for approval, just immediately redirect with code.
return
s
,
server
}
func
TestNewTestServer
(
t
*
testing
.
T
)
{
newTestServer
(
nil
)
newTestServer
(
t
,
nil
)
}
func
TestDiscovery
(
t
*
testing
.
T
)
{
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
())
defer
cancel
()
httpServer
,
_
:=
newTestServer
(
func
(
c
*
Config
)
{
httpServer
,
_
:=
newTestServer
(
t
,
func
(
c
*
Config
)
{
c
.
Issuer
=
c
.
Issuer
+
"/non-root-path"
})
defer
httpServer
.
Close
()
...
...
@@ -129,7 +129,7 @@ func TestOAuth2CodeFlow(t *testing.T) {
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
())
defer
cancel
()
httpServer
,
s
:=
newTestServer
(
func
(
c
*
Config
)
{
httpServer
,
s
:=
newTestServer
(
t
,
func
(
c
*
Config
)
{
c
.
Issuer
=
c
.
Issuer
+
"/non-root-path"
})
defer
httpServer
.
Close
()
...
...
@@ -255,7 +255,7 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
())
defer
cancel
()
httpServer
,
s
:=
newTestServer
(
func
(
c
*
Config
)
{
httpServer
,
s
:=
newTestServer
(
t
,
func
(
c
*
Config
)
{
// Enable support for the implicit flow.
c
.
SupportedResponseTypes
=
[]
string
{
"code"
,
"token"
}
})
...
...
server/templates.go
View file @
608d8ba9
package
server
import
(
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"sort"
"text/template"
)
"github.com/coreos/dex/storage"
const
(
tmplApproval
=
"approval.html"
tmplLogin
=
"login.html"
tmplPassword
=
"password.html"
)
const
coreOSLogoURL
=
"https://coreos.com/assets/images/brand/coreos-wordmark-135x40px.png"
var
requiredTmpls
=
[]
string
{
tmplApproval
,
tmplLogin
,
tmplPassword
,
}
// TemplateConfig describes.
type
TemplateConfig
struct
{
// Directory of the templates. If empty, these will be loaded from memory.
Dir
string
`yaml:"dir"`
// Defaults to the CoreOS logo and "dex".
LogoURL
string
`yaml:"logoURL"`
Issuer
string
`yaml:"issuerName"`
}
type
globalData
struct
{
LogoURL
string
Issuer
string
}
func
loadTemplates
(
config
TemplateConfig
)
(
*
templates
,
error
)
{
var
tmpls
*
template
.
Template
if
config
.
Dir
!=
""
{
files
,
err
:=
ioutil
.
ReadDir
(
config
.
Dir
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"read dir: %v"
,
err
)
}
filenames
:=
[]
string
{}
for
_
,
file
:=
range
files
{
if
file
.
IsDir
()
{
continue
}
filenames
=
append
(
filenames
,
filepath
.
Join
(
config
.
Dir
,
file
.
Name
()))
}
if
len
(
filenames
)
==
0
{
return
nil
,
fmt
.
Errorf
(
"no files in template dir %s"
,
config
.
Dir
)
}
if
tmpls
,
err
=
template
.
ParseFiles
(
filenames
...
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"parse files: %v"
,
err
)
}
}
else
{
// Load templates from memory. This code is largely copied from the standard library's
// ParseFiles source code.
// See: https://goo.gl/6Wm4mN
for
name
,
data
:=
range
defaultTemplates
{
var
t
*
template
.
Template
if
tmpls
==
nil
{
tmpls
=
template
.
New
(
name
)
}
if
name
==
tmpls
.
Name
()
{
t
=
tmpls
}
else
{
t
=
tmpls
.
New
(
name
)
}
if
_
,
err
:=
t
.
Parse
(
data
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"parsing %s: %v"
,
name
,
err
)
}
}
}
missingTmpls
:=
[]
string
{}
for
_
,
tmplName
:=
range
requiredTmpls
{
if
tmpls
.
Lookup
(
tmplName
)
==
nil
{
missingTmpls
=
append
(
missingTmpls
,
tmplName
)
}
}
if
len
(
missingTmpls
)
>
0
{
return
nil
,
fmt
.
Errorf
(
"missing template(s): %s"
,
missingTmpls
)
}
if
config
.
LogoURL
==
""
{
config
.
LogoURL
=
coreOSLogoURL
}
if
config
.
Issuer
==
""
{
config
.
Issuer
=
"dex"
}
return
&
templates
{
globalData
:
config
,
loginTmpl
:
tmpls
.
Lookup
(
tmplLogin
),
approvalTmpl
:
tmpls
.
Lookup
(
tmplApproval
),
passwordTmpl
:
tmpls
.
Lookup
(
tmplPassword
),
},
nil
}
var
scopeDescriptions
=
map
[
string
]
string
{
"offline_access"
:
"Have offline access"
,
"profile"
:
"View basic profile information"
,
"email"
:
"View your email"
,
}
type
templates
struct
{
globalData
TemplateConfig
loginTmpl
*
template
.
Template
approvalTmpl
*
template
.
Template
passwordTmpl
*
template
.
Template
}
type
connectorInfo
struct
{
DisplayName
string
ID
string
Name
string
URL
string
}
var
loginTmpl
=
template
.
Must
(
template
.
New
(
"login-template"
)
.
Parse
(
`<html>
<head></head>
<body>
<p>Login options</p>
{{ range $i, $connector := .Connectors }}
<a href="{{ $connector.URL }}?state={{ $.State }}">{{ $connector.DisplayName }}</a>
{{ end }}
</body>
</html>`
))
func
renderLoginOptions
(
w
http
.
ResponseWriter
,
connectors
[]
connectorInfo
,
state
string
)
{
type
byName
[]
connectorInfo
func
(
n
byName
)
Len
()
int
{
return
len
(
n
)
}
func
(
n
byName
)
Less
(
i
,
j
int
)
bool
{
return
n
[
i
]
.
Name
<
n
[
j
]
.
Name
}
func
(
n
byName
)
Swap
(
i
,
j
int
)
{
n
[
i
],
n
[
j
]
=
n
[
j
],
n
[
i
]
}
func
(
t
*
templates
)
login
(
w
http
.
ResponseWriter
,
connectors
[]
connectorInfo
,
state
string
)
{
sort
.
Sort
(
byName
(
connectors
))
data
:=
struct
{
TemplateConfig
Connectors
[]
connectorInfo
State
string
}{
connectors
,
state
}
renderTemplate
(
w
,
loginTmpl
,
data
)
}{
t
.
globalData
,
connectors
,
state
}
renderTemplate
(
w
,
t
.
loginTmpl
,
data
)
}
var
passwordTmpl
=
template
.
Must
(
template
.
New
(
"password-template"
)
.
Parse
(
`<html>
<body>
<p>Login</p>
<form action="{{ .Callback }}" method="POST">
Login: <input type="text" name="login"/><br/>
Password: <input type="password" name="password"/><br/>
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="submit"/>
{{ if .Message }}
<p>Error: {{ .Message }}</p>
{{ end }}
</form>
</body>
</html>`
))
func
renderPasswordTmpl
(
w
http
.
ResponseWriter
,
state
,
callback
,
message
string
)
{
func
(
t
*
templates
)
password
(
w
http
.
ResponseWriter
,
state
,
callback
,
lastUsername
string
,
lastWasInvalid
bool
)
{
data
:=
struct
{
TemplateConfig
State
string
Callback
string
Message
string
}{
state
,
callback
,
message
}
renderTemplate
(
w
,
passwordTmpl
,
data
)
PostURL
string
Username
string
Invalid
bool
}{
t
.
globalData
,
state
,
callback
,
lastUsername
,
lastWasInvalid
}
renderTemplate
(
w
,
t
.
passwordTmpl
,
data
)
}
var
approvalTmpl
=
template
.
Must
(
template
.
New
(
"approval-template"
)
.
Parse
(
`<html>
<body>
<p>User: {{ .User }}</p>
<p>Client: {{ .ClientName }}</p>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="approve">
<button type="submit">Approve</button>
</form>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="reject">
<button type="submit">Reject</button>
</form>
</body>
</html>`
))
func
renderApprovalTmpl
(
w
http
.
ResponseWriter
,
state
string
,
identity
storage
.
Claims
,
client
storage
.
Client
,
scopes
[]
string
)
{
func
(
t
*
templates
)
approval
(
w
http
.
ResponseWriter
,
state
,
username
,
clientName
string
,
scopes
[]
string
)
{
accesses
:=
[]
string
{}
for
_
,
scope
:=
range
scopes
{
access
,
ok
:=
scopeDescriptions
[
scope
]
if
ok
{
accesses
=
append
(
accesses
,
access
)
}
}
sort
.
Strings
(
accesses
)
data
:=
struct
{
TemplateConfig
User
string
Client
Name
string
Client
string
State
string
}{
identity
.
Email
,
client
.
Name
,
state
}
renderTemplate
(
w
,
approvalTmpl
,
data
)
Scopes
[]
string
}{
t
.
globalData
,
username
,
clientName
,
state
,
accesses
}
renderTemplate
(
w
,
t
.
approvalTmpl
,
data
)
}
func
renderTemplate
(
w
http
.
ResponseWriter
,
tmpl
*
template
.
Template
,
data
interface
{})
{
err
:=
tmpl
.
Execute
(
w
,
data
)
if
err
==
nil
{
return
}
// small io.Writer utilitiy to determine if executing the template wrote to the underlying response writer.
type
writeRecorder
struct
{
wrote
bool
w
io
.
Writer
}
switch
err
:=
err
.
(
type
)
{
case
template
.
ExecError
:
// An ExecError guarentees that Execute has not written to the underlying reader.
func
(
w
*
writeRecorder
)
Write
(
p
[]
byte
)
(
n
int
,
err
error
)
{
w
.
wrote
=
true
return
w
.
w
.
Write
(
p
)
}
func
renderTemplate
(
w
http
.
ResponseWriter
,
tmpl
*
template
.
Template
,
data
interface
{})
{
wr
:=
&
writeRecorder
{
w
:
w
}
if
err
:=
tmpl
.
Execute
(
wr
,
data
);
err
!=
nil
{
log
.
Printf
(
"Error rendering template %s: %s"
,
tmpl
.
Name
(),
err
)
if
!
wr
.
wrote
{
// TODO(ericchiang): replace with better internal server error.
http
.
Error
(
w
,
"Internal server error"
,
http
.
StatusInternalServerError
)
default
:
// An error with the underlying write, such as the connection being
// dropped. Ignore for now.
}
}
return
}
server/templates_test.go
View file @
608d8ba9
package
server
import
"testing"
func
TestNewTemplates
(
t
*
testing
.
T
)
{
var
config
TemplateConfig
if
_
,
err
:=
loadTemplates
(
config
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
func
TestLoadTemplates
(
t
*
testing
.
T
)
{
var
config
TemplateConfig
config
.
Dir
=
"../web/templates"
}
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