예제 #1
0
// CreateGrant generates the authorization code for 3rd-party clients to use
// in order to get access and refresh tokens, asking the resource owner for authorization.
func CreateGrant(w http.ResponseWriter, req *http.Request, cfg config) {
	provider := cfg.provider
	if yes := provider.IsUserAuthenticated(); !yes {
		u := cfg.loginURL.url
		query := u.Query()
		query.Set(cfg.loginURL.redirectParam, req.URL.String())
		u.RawQuery = query.Encode()

		http.Redirect(w, req, u.String(), http.StatusFound)
		return
	}

	vars := []string{"client_id", "state", "redirect_uri", "scope", "response_type"}
	params := make(map[string]string)
	for _, v := range vars {
		// FormValue also parses query string if method is GET
		params[v] = req.FormValue(v)
	}

	authzData := authCodeGrant1(w, req, cfg, params)
	if authzData == nil {
		// A response with an error was already sent back
		return
	}

	if req.Method == "GET" {
		// Displays authorization form to resource owner in order for her to
		// authorize 3rd-party client app.
		// TODO(c4milo): Figure out how to generate a CSRF token not tied to user's session
		render.HTML(w, render.Options{
			Status:    http.StatusOK,
			Data:      authzData,
			Template:  cfg.authzForm,
			STSMaxAge: cfg.stsMaxAge,
		})
		return
	}

	if params["response_type"] == "token" {
		// Continue with implicit grant flow
		implicitGrant(w, req, cfg, authzData)
		return
	}

	// 4.1.2.  Authorization Response
	// If the resource owner grants the access request, the authorization
	// server issues an authorization code and delivers it to the client by
	// adding the following parameters to the query component of the
	// redirection URI using the "application/x-www-form-urlencoded" format,
	// per Appendix B:
	// http://tools.ietf.org/html/rfc6749#section-4.2.1
	grant, err := provider.GenGrant(authzData.Client, authzData.Scopes, cfg.authzExpiration)
	if err != nil {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrServerError("", err),
				}},
			Template: cfg.authzForm,
		})
		return
	}

	u := authzData.Client.RedirectURL
	query := u.Query()
	query.Set("code", grant.Code)
	query.Set("state", authzData.State)
	u.RawQuery = query.Encode()

	// log.Printf("[DEBUG] Redirect to: %s", u.String())
	http.Redirect(w, req, u.String(), http.StatusFound)
}
예제 #2
0
// AuthCodeGrant1 implements http://tools.ietf.org/html/rfc6749#section-4.1.1 and
// http://tools.ietf.org/html/rfc6749#section-4.2.1
func authCodeGrant1(w http.ResponseWriter, req *http.Request, cfg config, params map[string]string) *AuthzData {
	provider := cfg.provider
	// If the client identifier is missing or invalid, the authorization server
	// SHOULD inform the resource owner of the error and MUST NOT automatically
	// redirect the user-agent to the invalid redirection URI.
	clientID := params["client_id"]
	if clientID == "" {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrClientIDMissing,
				},
			},
			Template: cfg.authzForm,
		})
		return nil
	}

	cinfo, err := provider.ClientInfo(clientID)
	if err != nil {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrServerError("", err),
				},
			},
			Template: cfg.authzForm,
		})
		return nil
	}

	if cinfo == (types.Client{}) {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrClientIDNotFound,
				},
			},
			Template: cfg.authzForm,
		})
		return nil
	}

	// If the request fails due to a missing, invalid, or mismatching
	// redirection URI, the authorization server SHOULD inform the resource
	// owner of the error and MUST NOT automatically redirect the user-agent to the
	// invalid redirection URI.
	var redirectURL *url.URL
	if u, ok := params["redirect_uri"]; ok {
		var err error
		redirectURL, err = url.Parse(u)
		if err != nil {
			// We are deliberately avoiding sending client original parameters,
			// so the authorization process is forced to start all over again.
			render.HTML(w, render.Options{
				Status: http.StatusOK,
				Data: AuthzData{
					Errors: []types.AuthzError{
						ErrRedirectURLInvalid,
					},
				},
				Template: cfg.authzForm,
			})
			return nil
		}
	} else {
		redirectURL = cinfo.RedirectURL
	}

	if redirectURL.Scheme != "https" {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrRedirectURLInvalid,
				},
			},
			Template: cfg.authzForm,
		})
		return nil
	}

	// The authorization server MUST verify that the redirection URI to which
	// it will redirect the authorization code or access token matches a redirection URI registered
	// by the client as described in Section 3.1.2.
	if redirectURL.String() != cinfo.RedirectURL.String() {
		render.HTML(w, render.Options{
			Status: http.StatusOK,
			Data: AuthzData{
				Errors: []types.AuthzError{
					ErrRedirectURLMismatch,
				},
			},
			Template: cfg.authzForm,
		})
		return nil
	}

	// An opaque value used by the client to maintain state between the request
	// and callback.  The authorization server includes this value when redirecting
	// the user-agent back to the client.  The parameter SHOULD be used for preventing
	// cross-site request forgery as described in Section 10.12.
	state := params["state"]
	if state == "" {
		EncodeErrInURI(redirectURL, ErrStateRequired(state))
		http.Redirect(w, req, redirectURL.String(), http.StatusFound)
		return nil
	}

	// response_type
	// Value MUST be set to "code" or "token" for implicit authorizations.
	grantType := params["response_type"]
	if grantType != "code" && grantType != "token" {
		EncodeErrInURI(redirectURL, ErrUnsupportedResponseType(state))
		http.Redirect(w, req, redirectURL.String(), http.StatusFound)
		return nil
	}

	// The scope of the access request as described by Section 3.3.
	scope := params["scope"]
	if scope == "" {
		EncodeErrInURI(redirectURL, ErrScopeRequired(state))
		http.Redirect(w, req, redirectURL.String(), http.StatusFound)
		return nil
	}

	scopes, err := provider.ScopesInfo(scope)
	if err != nil {
		EncodeErrInURI(redirectURL, ErrServerError(state, err))
		http.Redirect(w, req, redirectURL.String(), http.StatusFound)
		return nil
	}

	return &AuthzData{
		Client:    cinfo,
		Scopes:    scopes,
		GrantType: grantType,
		State:     state,
	}
}