示例#1
0
// TestSecurityHeaders makes sure security headers are sent along the authorization form.
func TestSecurityHeaders(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	state := "mystate"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"state":         {state},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	//log.Printf("%+v", w.HeaderMap)

	equals(t, "max-age=0", w.Header().Get("Strict-Transport-Security"))
	equals(t, "1; mode=block", w.Header().Get("X-XSS-Protection"))
	equals(t, "nosniff", w.Header().Get("X-Content-Type-Options"))
	equals(t, "SAMEORIGIN", w.Header().Get("X-Frame-Options"))
}
示例#2
0
// TestRedirectURIScheme makes sure clients provide redirect URLs that use TLS
func TestRedirectURIScheme(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {"http://attacker.com/callback"},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	body := w.Body.String()
	assert(t, strings.Contains(body, "access_denied") == true, "access-denied was not found in response body")
	assert(t, strings.Contains(body, "3rd-party client app provided an invalid redirect_uri. It does not comply with http://tools.ietf.org/html/rfc3986#section-4.3 or does not use HTTPS") == true, "error description does not match.")
}
示例#3
0
// TestResourceOwnerCredentialsGrant tests happy path for http://tools.ietf.org/html/rfc6749#section-4.3
func TestResourceOwnerCredentialsGrant(t *testing.T) {
	cfg := setupTest()
	cfg.provider = test.NewProvider(true)

	queryStr := url.Values{
		"grant_type": {"password"},
		"username":   {"test_user"},
		"password":   {"test_password"},
	}

	buffer := bytes.NewBufferString(queryStr.Encode())
	req, err := http.NewRequest("POST", "https://example.com/oauth2/tokens", buffer)
	ok(t, err)
	req.Header.Set("Content-type", "application/x-www-form-urlencoded")
	req.SetBasicAuth("testclient", "testclient")

	w := httptest.NewRecorder()
	IssueToken(w, req, cfg)

	accessToken := types.Token{}
	err = json.Unmarshal(w.Body.Bytes(), &accessToken)
	ok(t, err)

	//log.Printf("%s", w.Body.String())
	equals(t, "bearer", accessToken.Type)
	equals(t, "600", accessToken.ExpiresIn)

	assert(t, accessToken.RefreshToken != "", "we were expecting a refresh token.")

	// Tests that cache headers are being sent when generating tokens using
	// resource owner credentials.
	equals(t, "no-store", w.Header().Get("Cache-Control"))
	equals(t, "no-cache", w.Header().Get("Pragma"))
	equals(t, "0", w.Header().Get("Expires"))
}
示例#4
0
// TestStateIsRequired makes sure it requires clients to provide a state when
// getting authorization codes.
func TestStateIsRequired(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	equals(t, http.StatusFound, w.Code)
	u, err := url.Parse(w.Header().Get("Location"))
	ok(t, err)
	equals(t, "invalid_request", u.Query().Get("error"))
	equals(t, "state parameter is required by this authorization server.", u.Query().Get("error_description"))
}
示例#5
0
// TestRefreshToken tests happy path for http://tools.ietf.org/html/rfc6749#section-6
func TestRefreshToken(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	noAuthzGrant := types.Grant{
		Scopes: types.Scopes{
			types.Scope{ID: "identity"},
		},
	}
	accessToken, err := provider.GenToken(noAuthzGrant, types.Client{
		ID: "test_client_id",
	}, true, cfg.tokenExpiration)
	ok(t, err)

	queryStr := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {accessToken.RefreshToken},
		"scope":         {"identity"},
	}

	buffer := bytes.NewBufferString(queryStr.Encode())
	req, err := http.NewRequest("POST", "https://example.com/oauth2/tokens", buffer)
	ok(t, err)
	req.Header.Set("Content-type", "application/x-www-form-urlencoded")
	req.SetBasicAuth("testclient", "testclient")

	w := httptest.NewRecorder()
	IssueToken(w, req, cfg)

	token := types.Token{}
	err = json.Unmarshal(w.Body.Bytes(), &token)
	ok(t, err)

	//log.Printf("%s", w.Body.String())
	equals(t, "bearer", token.Type)
	equals(t, "600", token.ExpiresIn)
	assert(t, accessToken.Value != token.Value, "We got the same access token, it should be different!")
	assert(t, token.Value != "", "We were expecting to get a token and instead we got: %s", token.Value)
	assert(t, token.RefreshToken != "", "we were expecting a refresh token.")
	assert(t, token.RefreshToken != accessToken.RefreshToken, "We got the same refresh token, it should be different!")

	// Tests that cache headers are being sent when refreshing tokens
	equals(t, "no-store", w.Header().Get("Cache-Control"))
	equals(t, "no-cache", w.Header().Get("Pragma"))
	equals(t, "0", w.Header().Get("Expires"))
}
示例#6
0
// TestRedirectURLMatch makes sure redirect_uri for requesting an authorization
// grant is the same as the redirect_uri provided to get the correspondent access token.
// This is intended to mitigate the risk of account hijacking by leaking
// authorization codes.
func TestRedirectURLMatch(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	equals(t, http.StatusOK, w.Code)

	// Sending post to acquire authorization token
	values.Set("redirect_uri", "https://attacker.com/callback")
	queryStr2 := values.Encode()
	buffer := bytes.NewBufferString(queryStr2)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w2 := httptest.NewRecorder()
	CreateGrant(w2, req, cfg)
	body := w2.Body.String()
	assert(t, strings.Contains(body, "access_denied"), "access_denied was expected as response")
	assert(t, strings.Contains(body, "3rd-party client app provided a redirect_uri that does not match the URI registered for this client in our database."), "unexpected error description.")
}
示例#7
0
// TestLoginRedirect tests that logging in is required for a resource owner to
// grant any authorization codes to clients.
func TestLoginRedirect(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(false)
	cfg.provider = provider

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	equals(t, http.StatusFound, w.Code)

	loginURL := cfg.loginURL.url
	query := loginURL.Query()
	query.Set(cfg.loginURL.redirectParam, authzURL)
	loginURL.RawQuery = query.Encode()

	equals(t, loginURL.String(), w.Header().Get("Location"))
}
示例#8
0
// getTestAuthzCode returns authorization tokens for access tokens issuing tests
func getTestAuthzCode(t *testing.T) (config, string) {
	cfg := setupTest()

	provider := test.NewProvider(true)
	cfg.provider = provider

	state := "state-test"
	scopes := "read write identity"
	grantType := "code"

	values := url.Values{
		"client_id":     {provider.Client.ID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {provider.Client.RedirectURL.String()},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.1.1
	queryStr := values.Encode()
	req, err := http.NewRequest("GET",
		"https://example.com/oauth2/authzs?"+queryStr, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	equals(t, http.StatusOK, w.Code)

	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"code",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, cfg)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.1.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	authzCode := u.Query().Get("code")
	assert(t, authzCode != "", "It looks like the authorization code came back empty: %s", authzCode)

	// makes sure the same state parameter value received to acquire
	// the authorization grant is send back when delivering the access token.
	equals(t, state, u.Query().Get("state"))

	return cfg, authzCode
}
示例#9
0
// TestImplicitGrant tests a happy implicit flow
func TestImplicitGrant(t *testing.T) {
	cfg := setupTest()
	provider := test.NewProvider(true)
	cfg.provider = provider

	state := "state-test"
	scopes := "read write identity"
	grantType := "token"
	clientID := provider.Client.ID
	redirectURL := provider.Client.RedirectURL.String()

	values := url.Values{
		"client_id":     {clientID},
		"response_type": {grantType},
		"state":         {state},
		"redirect_uri":  {redirectURL},
		"scope":         {scopes},
	}

	// http://tools.ietf.org/html/rfc6749#section-4.2.1
	queryStr := values.Encode()
	authzURL := "https://example.com/oauth2/authzs?" + queryStr
	req, err := http.NewRequest("GET", authzURL, nil)
	ok(t, err)

	w := httptest.NewRecorder()
	CreateGrant(w, req, cfg)
	body := w.Body.String()
	stringz := []string{
		"client_id",
		"redirect_uri",
		"response_type",
		"state",
		"scope",
		"token",
		"read write identity",
		"state-test",
	}

	for _, s := range stringz {
		assert(t, strings.Contains(body, s), "Does not look like we got an authorization form: '%s' was not found in %v", s, body)
	}

	// Sending post to acquire authorization token
	buffer := bytes.NewBufferString(queryStr)
	req, err = http.NewRequest("POST", "https://example.com/oauth2/authzs", buffer)
	ok(t, err)

	req.Header.Set("Content-type", "application/x-www-form-urlencoded")

	w = httptest.NewRecorder()
	CreateGrant(w, req, cfg)

	// Tests http://tools.ietf.org/html/rfc6749#section-4.2.2
	equals(t, http.StatusFound, w.Code)

	redirectTo := w.Header().Get("Location")
	u, err := url.Parse(redirectTo)
	ok(t, err)

	fragment, err := url.ParseQuery(strings.TrimPrefix(u.Fragment, "#"))
	ok(t, err)
	accessToken := fragment.Get("access_token")
	assert(t, accessToken != "", "It looks like the authorization code came back empty: ->%s<-", accessToken)
	equals(t, state, fragment.Get("state"))
	equals(t, "600", fragment.Get("expires_in"))
	equals(t, scopes, fragment.Get("scope"))
	equals(t, "bearer", fragment.Get("token_type"))

	// Implict flow should not emit refresh tokens
	refreshToken := fragment.Get("refresh_token")
	equals(t, "", refreshToken)
}
示例#10
0
func ExampleExamples_basic() {
	// Authorization form
	authzForm := `
		<html>
		<body>
		{{if .Errors}}
			<div id="errors">
				<ul>
				{{range .Errors}}
					<li>{{.Code}}: {{.Desc}}</li>
				{{end}}
				</ul>
			</div>
		{{else}}
			<div id="client">
				<h2>{{.Client.Name}}</h2>
				<h3>{{.Client.Desc}}</h3>
				<a href="{{.Client.HomepageURL}}">
					<figure><img src="{{.Client.ProfileImgURL}}"/></figure>
				</a>
			</div>
			<div id="scopes">
				<ul>
					{{range .Scopes}}
						<li>{{.ID}}: {{.Desc}}</li>
					{{end}}
				</ul>
			</div>
			<form>
			 <input type="hidden" name="client_id" value="{{.Client.ID}}"/>
			 <input type="hidden" name="response_type" value="{{.GrantType}}"/>
			 <input type="hidden" name="redirect_uri" value="{{.Client.RedirectURL}}"/>
			 <input type="hidden" name="scope" value="{{.Scopes.Encode}}"/>
			 <input type="hidden" name="state" value="{{.State}}"/>
			</form>
		{{end}}
		</body>
		</html>
	`

	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
		w.Write([]byte("Hellow World!"))
	})

	provider := test.NewProvider(true)
	// Authorization handler to protect resources in this server
	authzHandler := AuthzHandler(mux, provider)
	// OAuth2 handler
	oauth2Handlers := Handler(authzHandler,
		SetProvider(provider),
		SetAuthzForm(authzForm),
		SetAuthzEndpoint("/oauth2/authorize"),
		SetTokenEndpoint("/oauth2/tokens"),
		SetSTSMaxAge(time.Duration(8760)*time.Hour), // 1yr
		SetAuthzExpiration(time.Duration(1)*time.Minute),
		SetTokenExpiration(time.Duration(10)*time.Minute),
		SetLoginURL("https://api.hooklift.io/accounts/login", "redirect_to"),
	)

	log.Fatal(http.ListenAndServe(":3000", oauth2Handlers))
}