Skip to content

Commit 3b75f6b

Browse files
committed
Add CSRF to token request endpoint
Signed-off-by: Monis Khan <mkhan@redhat.com>
1 parent f69f034 commit 3b75f6b

2 files changed

Lines changed: 117 additions & 13 deletions

File tree

pkg/oauthserver/oauthserver/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (c *OAuthServerConfig) WithOAuth(handler http.Handler) (http.Handler, error
149149
loginURL = c.ExtraOAuthConfig.Options.MasterPublicURL
150150
}
151151

152-
tokenRequestEndpoints := tokenrequest.NewTokenRequest(loginURL, openShiftLogoutPrefix, c.getOsinOAuthClient, c.ExtraOAuthConfig.OAuthAccessTokenClient)
152+
tokenRequestEndpoints := tokenrequest.NewTokenRequest(loginURL, openShiftLogoutPrefix, c.getOsinOAuthClient, c.ExtraOAuthConfig.OAuthAccessTokenClient, c.getCSRF())
153153
tokenRequestEndpoints.Install(mux, urls.OpenShiftOAuthAPIPrefix)
154154

155155
if session := c.ExtraOAuthConfig.SessionAuth; session != nil {

pkg/oauthserver/server/tokenrequest/tokenrequest.go

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import (
55
"html/template"
66
"io"
77
"net/http"
8+
"net/url"
89
"path"
910

1011
"github.com/RangelReale/osincli"
1112

1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
15+
"k8s.io/klog"
1416

1517
"github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
1618
"github.com/openshift/origin/pkg/oauth/urls"
1719
"github.com/openshift/origin/pkg/oauthserver"
1820
"github.com/openshift/origin/pkg/oauthserver/authenticator/password/bootstrap"
21+
"github.com/openshift/origin/pkg/oauthserver/server/csrf"
1922
)
2023

24+
const csrfParam = "csrf"
25+
2126
type tokenRequest struct {
2227
publicMasterURL string
2328
// osinOAuthClientGetter is used to initialize osinOAuthClient.
@@ -27,14 +32,17 @@ type tokenRequest struct {
2732
// to check if we need the logout link for the bootstrap user
2833
tokens v1.OAuthAccessTokenInterface
2934
openShiftLogoutPrefix string
35+
36+
csrf csrf.CSRF
3037
}
3138

32-
func NewTokenRequest(publicMasterURL, openShiftLogoutPrefix string, osinOAuthClientGetter func() (*osincli.Client, error), tokens v1.OAuthAccessTokenInterface) oauthserver.Endpoints {
39+
func NewTokenRequest(publicMasterURL, openShiftLogoutPrefix string, osinOAuthClientGetter func() (*osincli.Client, error), tokens v1.OAuthAccessTokenInterface, csrf csrf.CSRF) oauthserver.Endpoints {
3340
return &tokenRequest{
3441
publicMasterURL: publicMasterURL,
3542
osinOAuthClientGetter: osinOAuthClientGetter,
3643
tokens: tokens,
3744
openShiftLogoutPrefix: openShiftLogoutPrefix,
45+
csrf: csrf,
3846
}
3947
}
4048

@@ -65,15 +73,47 @@ func (t *tokenRequest) requestToken(osinOAuthClient *osincli.Client, w http.Resp
6573
}
6674

6775
func (t *tokenRequest) displayToken(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
68-
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
69-
requestURL := urls.OpenShiftOAuthTokenRequestURL("") // relative url to token request endpoint
70-
data := tokenData{RequestURL: requestURL, PublicMasterURL: t.publicMasterURL}
76+
switch req.Method {
77+
case http.MethodGet:
78+
t.displayTokenGet(osinOAuthClient, w, req)
79+
case http.MethodPost:
80+
t.displayTokenPost(osinOAuthClient, w, req)
81+
default:
82+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
83+
}
84+
}
7185

72-
authorizeReq := osinOAuthClient.NewAuthorizeRequest(osincli.CODE)
73-
authorizeData, err := authorizeReq.HandleRequest(req)
86+
func (t *tokenRequest) displayTokenGet(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
87+
data := formData{}
88+
authorizeData, ok := displayTokenStart(osinOAuthClient, w, req, &data.sharedData)
89+
if !ok {
90+
renderForm(w, data)
91+
return
92+
}
93+
94+
uri, err := getBaseURL(req)
7495
if err != nil {
75-
data.Error = fmt.Sprintf("Error handling auth request: %v", err)
76-
w.WriteHeader(http.StatusInternalServerError)
96+
utilruntime.HandleError(fmt.Errorf("unable to generate base URL: %v", err))
97+
http.Error(w, "Unable to determine URL", http.StatusInternalServerError)
98+
return
99+
}
100+
101+
data.Action = uri.String()
102+
data.Code = authorizeData.Code
103+
data.CSRF = t.csrf.Generate(w, req)
104+
renderForm(w, data)
105+
}
106+
107+
func (t *tokenRequest) displayTokenPost(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
108+
if ok := t.csrf.Check(req, req.FormValue(csrfParam)); !ok {
109+
klog.V(4).Infof("Invalid CSRF token: %s", req.FormValue(csrfParam))
110+
http.Error(w, "Could not check CSRF token. Please try again.", http.StatusBadRequest)
111+
return
112+
}
113+
114+
data := tokenData{PublicMasterURL: t.publicMasterURL}
115+
authorizeData, ok := displayTokenStart(osinOAuthClient, w, req, &data.sharedData)
116+
if !ok {
77117
renderToken(w, data)
78118
return
79119
}
@@ -104,22 +144,66 @@ func (t *tokenRequest) displayToken(osinOAuthClient *osincli.Client, w http.Resp
104144
renderToken(w, data)
105145
}
106146

147+
func displayTokenStart(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request, data *sharedData) (*osincli.AuthorizeData, bool) {
148+
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
149+
150+
requestURL := urls.OpenShiftOAuthTokenRequestURL("") // relative url to token request endpoint
151+
data.RequestURL = requestURL // always set this field even on error cases
152+
153+
authorizeReq := osinOAuthClient.NewAuthorizeRequest(osincli.CODE)
154+
authorizeData, err := authorizeReq.HandleRequest(req)
155+
if err != nil {
156+
w.WriteHeader(http.StatusBadRequest)
157+
data.Error = fmt.Sprintf("Error handling auth request: %v", err)
158+
return nil, false
159+
}
160+
161+
return authorizeData, true
162+
}
163+
107164
func renderToken(w io.Writer, data tokenData) {
108165
if err := tokenTemplate.Execute(w, data); err != nil {
109166
utilruntime.HandleError(fmt.Errorf("unable to render token template: %v", err))
110167
}
111168
}
112169

170+
type sharedData struct {
171+
Error string
172+
RequestURL string
173+
}
174+
113175
type tokenData struct {
114-
Error string
176+
sharedData
177+
115178
AccessToken string
116-
RequestURL string
117179
PublicMasterURL string
118180
LogoutURL string
119181
}
120182

121-
// TODO: allow template to be read from an external file
122-
var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(`
183+
func getBaseURL(req *http.Request) (*url.URL, error) {
184+
uri, err := url.Parse(req.RequestURI)
185+
if err != nil {
186+
return nil, err
187+
}
188+
uri.Scheme, uri.Host, uri.RawQuery, uri.Fragment = req.URL.Scheme, req.URL.Host, "", ""
189+
return uri, nil
190+
}
191+
192+
type formData struct {
193+
sharedData
194+
195+
Action string
196+
Code string
197+
CSRF string
198+
}
199+
200+
func renderForm(w io.Writer, data formData) {
201+
if err := formTemplate.Execute(w, data); err != nil {
202+
utilruntime.HandleError(fmt.Errorf("unable to render form template: %v", err))
203+
}
204+
}
205+
206+
const cssStyle = `
123207
<style>
124208
body { font-family: sans-serif; font-size: 14px; margin: 2em 2%; background-color: #F9F9F9; }
125209
h2 { font-size: 1.4em;}
@@ -135,7 +219,10 @@ var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(`
135219
.nowrap { white-space: nowrap; }
136220
}
137221
</style>
222+
`
138223

224+
var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(
225+
cssStyle + `
139226
{{ if .Error }}
140227
{{ .Error }}
141228
{{ else }}
@@ -163,6 +250,23 @@ var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(`
163250
{{ end }}
164251
`))
165252

253+
var formTemplate = template.Must(template.New("formTemplate").Parse(
254+
cssStyle + `
255+
{{ if .Error }}
256+
{{ .Error }}
257+
<br><br>
258+
<a href="{{.RequestURL}}">Request another token</a>
259+
{{ else }}
260+
<form method="post" action="{{.Action}}">
261+
<input type="hidden" name="code" value="{{.Code}}">
262+
<input type="hidden" name="csrf" value="{{.CSRF}}">
263+
<button type="submit">
264+
Display Token
265+
</button>
266+
</form>
267+
{{ end }}
268+
`))
269+
166270
func (t *tokenRequest) implicitToken(w http.ResponseWriter, req *http.Request) {
167271
w.Header().Set("Content-Type", "text/plain")
168272
_, _ = w.Write([]byte(`

0 commit comments

Comments
 (0)