Beispiel #1
0
// IssueToken handles all requests going to tokens endpoint.
func IssueToken(w http.ResponseWriter, req *http.Request, cfg config) {
	provider := cfg.provider
	username, password, ok := req.BasicAuth()
	cinfo, err := provider.AuthenticateClient(username, password)
	if !ok || err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrUnauthorizedClient,
		})
		return
	}

	grantType := req.FormValue("grant_type")
	switch grantType {
	case "authorization_code":
		authCodeGrant2(w, req, cfg, cinfo)
	case "client_credentials":
		clientCredentialsGrant(w, req, cfg, cinfo)
	case "password":
		resourceOwnerCredentialsGrant(w, req, cfg, cinfo)
	case "refresh_token":
		refreshToken(w, req, cfg, cinfo)
	default:
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrUnsupportedGrantType,
		})
		return
	}
}
Beispiel #2
0
// Implements http://tools.ietf.org/html/rfc6749#section-4.4
func clientCredentialsGrant(w http.ResponseWriter, req *http.Request, cfg config, cinfo types.Client) {
	provider := cfg.provider
	scope := req.FormValue("scope")
	var scopes types.Scopes
	if scope != "" {
		var err error
		scopes, err = provider.ScopesInfo(scope)
		if err != nil {
			render.JSON(w, render.Options{
				Status: http.StatusBadRequest,
				Data:   ErrServerError("", err),
			})
			return
		}
	}

	noAuthzGrant := types.Grant{
		Scopes: scopes,
	}
	token, err := provider.GenToken(noAuthzGrant, cinfo, false, cfg.tokenExpiration)
	if err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusInternalServerError,
			Data:   ErrServerError("", err),
		})
		return
	}

	render.JSON(w, render.Options{
		Status: http.StatusOK,
		Data:   token,
	})
}
Beispiel #3
0
// Implements https://tools.ietf.org/html/rfc7009
// It does not take into account token_type_hint as the common use case is to
// have access and refresh tokens uniquely identified throughout the system. That said,
// unsupported_token_type error responses are not produced by this implementation either.
func RevokeToken(w http.ResponseWriter, req *http.Request, cfg config) {
	provider := cfg.provider
	username, password, ok := req.BasicAuth()
	cinfo, err := provider.AuthenticateClient(username, password)
	if !ok || err != nil {
		// TODO(c4milo): verify other implementations to see if they reply
		// with 401 instead of 400. Spec is sort of contradictory in this regard.
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrUnauthorizedClient,
		})
		return
	}

	token := path.Base(req.URL.Path)
	tokenInfo, err := provider.TokenInfo(token)
	if err != nil {
		log.Printf("[ERROR] Error getting token info: %+v", err)
		render.JSON(w, render.Options{
			Status: http.StatusServiceUnavailable,
		})
		return
	}

	if tokenInfo.ClientID != cinfo.ID {
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrClientIDMismatch,
		})
		return
	}

	err = provider.RevokeToken(token)
	if err != nil {
		log.Printf("[ERROR] Error revoking token: %+v", err)
		render.JSON(w, render.Options{
			Status: http.StatusServiceUnavailable,
		})
		return
	}

	render.JSON(w, render.Options{
		Status: http.StatusOK,
	})
}
Beispiel #4
0
// Implements http://tools.ietf.org/html/rfc6749#section-4.3
func resourceOwnerCredentialsGrant(w http.ResponseWriter, req *http.Request, cfg config, cinfo types.Client) {
	provider := cfg.provider
	if ok := provider.AuthenticateUser(req.FormValue("username"), req.FormValue("password")); !ok {
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrUnathorizedUser,
		})
		return
	}

	scope := req.FormValue("scope")
	var scopes types.Scopes
	if scope != "" {
		var err error
		scopes, err = provider.ScopesInfo(scope)
		if err != nil {
			render.JSON(w, render.Options{
				Status: http.StatusBadRequest,
				Data:   ErrServerError("", err),
			})
			return
		}
	}

	noAuthzGrant := types.Grant{
		Scopes: scopes,
	}
	token, err := provider.GenToken(noAuthzGrant, cinfo, true, cfg.tokenExpiration)
	if err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusInternalServerError,
			Data:   ErrServerError("", err),
		})
		return
	}

	render.JSON(w, render.Options{
		Status: http.StatusOK,
		Data:   token,
	})
}
Beispiel #5
0
// Implements http://tools.ietf.org/html/rfc6749#section-4.1.3,
// http://tools.ietf.org/html/rfc6749#section-4.1.4 and
// http://tools.ietf.org/html/rfc6749#section-5.2
//
// Implementation notes:
//  * Ignores client_id as we are always requiring the client to authenticate
//  * Ignores redirect_uri as we force a static and pre-registered redirect URI for the client
func authCodeGrant2(w http.ResponseWriter, req *http.Request, cfg config, cinfo types.Client) {
	provider := cfg.provider
	code := req.FormValue("code")
	if code == "" {
		err := ErrUnauthorizedClient
		err.Description = "Authorization code can't be empty."
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrUnauthorizedClient,
		})
		return
	}

	grant, err := provider.GrantInfo(code)
	if err != nil {
		e := ErrInvalidGrant
		e.Description = err.Error()

		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   e,
		})
		return
	}

	if grant.Status == types.GrantRevoked ||
		grant.Status == types.GrantExpired ||
		grant.Status == types.GrantUsed {
		e := ErrInvalidGrant
		e.Description = "Grant code was revoked, expired or already used."

		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   e,
		})
		return
	}

	if cinfo.RedirectURL.String() != grant.RedirectURL.String() {
		e := ErrInvalidGrant
		e.Description = "Grant code was generated for a different redirect URI."

		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   e,
		})
		return
	}

	// This should not happen if the provider is doing its work properly but we are
	// checking anyways.
	if grant.ClientID != cinfo.ID {
		e := ErrInvalidGrant
		e.Description = "Grant code was generated for a different client ID."

		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   e,
		})
		return
	}

	token, err := provider.GenToken(grant, cinfo, true, cfg.tokenExpiration)
	if err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusInternalServerError,
			Data:   ErrServerError("", err),
		})
		return
	}

	render.JSON(w, render.Options{
		Status: http.StatusOK,
		Data:   token,
	})
}
Beispiel #6
0
// Implements http://tools.ietf.org/html/rfc6749#section-6
func refreshToken(w http.ResponseWriter, req *http.Request, cfg config, cinfo types.Client) {
	provider := cfg.provider
	code := req.FormValue("refresh_token")
	token, err := provider.TokenInfo(code)
	if err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusInternalServerError,
			Data:   ErrServerError("", err),
		})
		return
	}

	scope := req.FormValue("scope")
	var scopes types.Scopes
	if scope != "" {
		var err error
		scopes, err = provider.ScopesInfo(scope)
		if err != nil {
			render.JSON(w, render.Options{
				Status: http.StatusInternalServerError,
				Data:   ErrServerError("", err),
			})
			return
		}

		// The requested scope MUST NOT include any scope not originally granted
		// by the resource owner, and if omitted is treated as equal to the scope
		// originally granted by the resource owner.
		tscopes := token.Scopes.Encode()
		for _, s := range scopes {
			// TODO(c4milo): make more robust
			if !strings.Contains(tscopes, s.ID) {
				render.JSON(w, render.Options{
					Status: http.StatusBadRequest,
					Data:   ErrInvalidScope,
				})
				return
			}
		}
	}

	if len(scopes) == 0 {
		scopes = token.Scopes
	}

	if token.ClientID != cinfo.ID {
		render.JSON(w, render.Options{
			Status: http.StatusBadRequest,
			Data:   ErrClientIDMismatch,
		})
		return
	}

	newToken, err := provider.RefreshToken(token, scopes)
	if err != nil {
		render.JSON(w, render.Options{
			Status: http.StatusInternalServerError,
			Data:   ErrServerError("", err),
		})
		return
	}

	render.JSON(w, render.Options{
		Status: http.StatusOK,
		Data:   newToken,
	})
}