Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 77 additions & 84 deletions pkg/server/tokenrequest/tokenrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import (
"html/template"
"io"
"net/http"
"net/url"
"path"

"github.com/openshift/osincli"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/klog/v2"

v1 "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
bootstrap "github.com/openshift/library-go/pkg/authentication/bootstrapauthenticator"
Expand All @@ -24,8 +22,6 @@ import (
"github.com/openshift/oauth-server/pkg/server/csrf"
)

const csrfParam = "csrf"

type tokenRequest struct {
publicMasterURL string
// osinOAuthClientGetter is used to initialize osinOAuthClient.
Expand Down Expand Up @@ -76,41 +72,8 @@ func (t *tokenRequest) requestToken(osinOAuthClient *osincli.Client, w http.Resp
}

func (t *tokenRequest) displayToken(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodGet:
t.displayTokenGet(osinOAuthClient, w, req)
case http.MethodPost:
t.displayTokenPost(osinOAuthClient, w, req)
default:
if req.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
Comment on lines -79 to 76

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a breaking change - is there a particular reason we are making this change?

}
}

func (t *tokenRequest) displayTokenGet(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
data := formData{}
authorizeData, ok := displayTokenStart(osinOAuthClient, w, req, &data.sharedData)
if !ok {
renderForm(w, data)
return
}

uri, err := getBaseURL(req)
if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to generate base URL: %v", err))
http.Error(w, "Unable to determine URL", http.StatusInternalServerError)
return
}

data.Action = uri.String()
data.Code = authorizeData.Code
data.CSRF = t.csrf.Generate(w, req)
renderForm(w, data)
}

func (t *tokenRequest) displayTokenPost(osinOAuthClient *osincli.Client, w http.ResponseWriter, req *http.Request) {
if ok := t.csrf.Check(req, req.FormValue(csrfParam)); !ok {
klog.V(4).Infof("Invalid CSRF token: %s", req.FormValue(csrfParam))
http.Error(w, "Could not check CSRF token. Please try again.", http.StatusBadRequest)
return
}

Expand Down Expand Up @@ -144,6 +107,8 @@ func (t *tokenRequest) displayTokenPost(osinOAuthClient *osincli.Client, w http.
}

data.AccessToken = accessData.AccessToken
data.AccessTokenJSStr = template.JSStr(data.AccessToken)
data.PublicMasterURLJSStr = template.JSStr(data.PublicMasterURL)
renderToken(w, data)
}

Expand Down Expand Up @@ -178,32 +143,11 @@ type sharedData struct {
type tokenData struct {
sharedData

AccessToken string
PublicMasterURL string
LogoutURL string
}

func getBaseURL(req *http.Request) (*url.URL, error) {
uri, err := url.Parse(req.RequestURI)
if err != nil {
return nil, err
}
uri.Scheme, uri.Host, uri.RawQuery, uri.Fragment = req.URL.Scheme, req.URL.Host, "", ""
return uri, nil
}

type formData struct {
sharedData

Action string
Code string
CSRF string
}

func renderForm(w io.Writer, data formData) {
if err := formTemplate.Execute(w, data); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to render form template: %v", err))
}
AccessToken string
AccessTokenJSStr template.JSStr
PublicMasterURL string
PublicMasterURLJSStr template.JSStr
LogoutURL string
}

const cssStyle = `
Expand All @@ -214,6 +158,9 @@ const cssStyle = `
code,pre { font-family: Menlo, Monaco, Consolas, monospace; }
code { font-weight: 300; font-size: 1.5em; margin-bottom: 1em; display: inline-block; color: #646464; }
pre { padding-left: 1em; border-radius: 5px; color: #003d6e; background-color: #EAEDF0; padding: 1.5em 0 1.5em 4.5em; white-space: normal; text-indent: -2em; }
pre>button { margin-left: 1.5em; margin-right: 1.5em; float: right; }
pre>button:disabled { color: #444; }
pre>button:disabled:hover { text-decoration: none; cursor: default; }
a { color: #00f; text-decoration: none; }
a:hover { text-decoration: underline; }
button { background: none; border: none; color: #00f; text-decoration: none; font: inherit; padding: 0; }
Expand All @@ -229,14 +176,77 @@ var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(
{{ if .Error }}
{{ .Error }}
{{ else }}
<script>
function codeSnippet(e, textBlocks) {
const snippet = textBlocks.join(' ');

const snippetHTML = textBlocks
.map((text) => '<span class="nowrap">' + text + '</span>')
.join(' ');

const copyButton = document.createElement('button');
copyButton.innerText = 'Copy';

copyButton.onclick = () => {
copyButton.disabled = true;
navigator.clipboard.writeText(snippet)
.then(() => {
copyButton.innerText = 'Copied!';
setTimeout(() => {
copyButton.innerText = 'Copy';
copyButton.disabled = false;
}, 3000);
})
.catch((error) => {
copyButton.innerText = 'Error!';
setTimeout(() => {
copyButton.innerText = 'Copy';
copyButton.disabled = false;
}, 3000);
console.error('Failed to copy snippet to clipboard', error);
});
};

const showButton = document.createElement('button');
showButton.innerText = 'Show';

showButton.onclick = () => {
e.innerHTML = snippetHTML;
e.appendChild(copyButton);
};

e.innerHTML = '***';
e.appendChild(copyButton);
e.appendChild(showButton);
}
</script>

<h2>Your API token is</h2>
<code>{{.AccessToken}}</code>
<pre id="token"></pre>

<h2>Log in with this token</h2>
<pre>oc login <span class="nowrap">--token={{.AccessToken}}</span> <span class="nowrap">--server={{.PublicMasterURL}}</span></pre>
<pre id="login"></pre>

<h3>Use this token directly against the API</h3>
<pre>curl <span class="nowrap">-H "Authorization: Bearer {{.AccessToken}}"</span> <span class="nowrap">"{{.PublicMasterURL}}/apis/user.openshift.io/v1/users/~"</span></pre>
<pre id="apiCall"></pre>

<script>
codeSnippet(document.getElementById('token'), [
'{{.AccessTokenJSStr}}',
]);

codeSnippet(document.getElementById('login'), [
'oc login',
'--token={{.AccessTokenJSStr}}',
'--server={{.PublicMasterURLJSStr}}',
]);

codeSnippet(document.getElementById('apiCall'), [
'curl',
'-H "Authorization: Bearer {{.AccessTokenJSStr}}"',
'"{{.PublicMasterURLJSStr}}/apis/user.openshift.io/v1/users/~"',
]);
</script>
{{ end }}

<br><br>
Expand All @@ -253,23 +263,6 @@ var tokenTemplate = template.Must(template.New("tokenTemplate").Parse(
{{ end }}
`))

var formTemplate = template.Must(template.New("formTemplate").Parse(
cssStyle + `
{{ if .Error }}
{{ .Error }}
<br><br>
<a href="{{.RequestURL}}">Request another token</a>
{{ else }}
<form method="post" action="{{.Action}}">
<input type="hidden" name="code" value="{{.Code}}">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<button type="submit">
Display Token
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't remove this. This was there to prevent cross-site request forgery.

/hold

</button>
</form>
{{ end }}
`))

func (t *tokenRequest) implicitToken(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte(`
Expand Down