The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/contexthandler/contexthandler.go

739 lines
23 KiB

// Package contexthandler contains the ContextHandler service.
package contexthandler
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/components/apikeygen"
apikeygenprefix "github.com/grafana/grafana/pkg/components/apikeygenprefixed"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/network"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/infra/tracing"
loginpkg "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
const (
InvalidUsernamePassword = "invalid username or password"
/* #nosec */
InvalidAPIKey = "invalid API key"
)
const ServiceName = "ContextHandler"
func ProvideService(cfg *setting.Cfg, tokenService auth.UserTokenService, jwtService models.JWTService,
remoteCache *remotecache.RemoteCache, renderService rendering.Service, sqlStore db.DB,
tracer tracing.Tracer, authProxy *authproxy.AuthProxy, loginService login.Service,
apiKeyService apikey.Service, authenticator loginpkg.Authenticator, userService user.Service,
orgService org.Service, oauthTokenService oauthtoken.OAuthTokenService, features *featuremgmt.FeatureManager,
) *ContextHandler {
return &ContextHandler{
Cfg: cfg,
AuthTokenService: tokenService,
JWTAuthService: jwtService,
RemoteCache: remoteCache,
RenderService: renderService,
SQLStore: sqlStore,
tracer: tracer,
authProxy: authProxy,
authenticator: authenticator,
loginService: loginService,
apiKeyService: apiKeyService,
userService: userService,
orgService: orgService,
oauthTokenService: oauthTokenService,
features: features,
}
}
// ContextHandler is a middleware.
type ContextHandler struct {
Cfg *setting.Cfg
AuthTokenService auth.UserTokenService
JWTAuthService models.JWTService
RemoteCache *remotecache.RemoteCache
RenderService rendering.Service
SQLStore db.DB
tracer tracing.Tracer
authProxy *authproxy.AuthProxy
authenticator loginpkg.Authenticator
loginService login.Service
apiKeyService apikey.Service
userService user.Service
orgService org.Service
oauthTokenService oauthtoken.OAuthTokenService
features *featuremgmt.FeatureManager
// GetTime returns the current time.
// Stubbable by tests.
GetTime func() time.Time
}
type reqContextKey = ctxkey.Key
// FromContext returns the ReqContext value stored in a context.Context, if any.
func FromContext(c context.Context) *models.ReqContext {
if reqCtx, ok := c.Value(reqContextKey{}).(*models.ReqContext); ok {
return reqCtx
}
return nil
}
// Middleware provides a middleware to initialize the request context.
func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
mContext := web.FromContext(ctx)
_, span := h.tracer.Start(ctx, "Auth - Middleware")
defer span.End()
reqContext := &models.ReqContext{
Context: mContext,
SignedInUser: &user.SignedInUser{},
IsSignedIn: false,
AllowAnonymous: false,
SkipCache: false,
Logger: log.New("context"),
}
// Inject ReqContext into http.Request.Context
*r = *r.WithContext(context.WithValue(ctx, reqContextKey{}, reqContext))
traceID := tracing.TraceIDFromContext(mContext.Req.Context(), false)
if traceID != "" {
reqContext.Logger = reqContext.Logger.New("traceID", traceID)
}
const headerName = "X-Grafana-Org-Id"
orgID := int64(0)
orgIDHeader := reqContext.Req.Header.Get(headerName)
if orgIDHeader != "" {
id, err := strconv.ParseInt(orgIDHeader, 10, 64)
if err == nil {
orgID = id
} else {
reqContext.Logger.Debug("Received invalid header", "header", headerName, "value", orgIDHeader)
}
}
queryParameters, err := url.ParseQuery(reqContext.Req.URL.RawQuery)
if err != nil {
reqContext.Logger.Error("Failed to parse query parameters", "error", err)
}
if queryParameters.Has("targetOrgId") {
targetOrg, err := strconv.ParseInt(queryParameters.Get("targetOrgId"), 10, 64)
if err == nil {
orgID = targetOrg
} else {
reqContext.Logger.Error("Invalid target organization ID", "error", err)
}
}
// the order in which these are tested are important
// look for api key in Authorization header first
// then init session and look for userId in session
// then look for api key in session (special case for render calls via api)
// then test if anonymous access is enabled
switch {
case h.initContextWithRenderAuth(reqContext):
case h.initContextWithJWT(reqContext, orgID):
case h.initContextWithAPIKey(reqContext):
case h.initContextWithBasicAuth(reqContext, orgID):
case h.initContextWithAuthProxy(reqContext, orgID):
case h.initContextWithToken(reqContext, orgID):
case h.initContextWithAnonymousUser(reqContext):
}
reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserID, "orgId", reqContext.OrgID, "uname", reqContext.Login)
span.AddEvents(
[]string{"uname", "orgId", "userId"},
[]tracing.EventValue{
{Str: reqContext.Login},
{Num: reqContext.OrgID},
{Num: reqContext.UserID}},
)
// update last seen every 5min
if reqContext.ShouldUpdateLastSeenAt() {
reqContext.Logger.Debug("Updating last user_seen_at", "user_id", reqContext.UserID)
if err := h.userService.UpdateLastSeenAt(mContext.Req.Context(), &user.UpdateUserLastSeenAtCommand{UserID: reqContext.UserID}); err != nil {
reqContext.Logger.Error("Failed to update last_seen_at", "error", err)
}
}
next.ServeHTTP(w, r)
})
}
func (h *ContextHandler) initContextWithAnonymousUser(reqContext *models.ReqContext) bool {
if !h.Cfg.AnonymousEnabled {
return false
}
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAnonymousUser")
defer span.End()
getOrg := org.GetOrgByNameQuery{Name: h.Cfg.AnonymousOrgName}
orga, err := h.orgService.GetByName(reqContext.Req.Context(), &getOrg)
if err != nil {
reqContext.Logger.Error("Anonymous access organization error.", "org_name", h.Cfg.AnonymousOrgName, "error", err)
return false
}
reqContext.IsSignedIn = false
reqContext.AllowAnonymous = true
reqContext.SignedInUser = &user.SignedInUser{IsAnonymous: true}
reqContext.OrgRole = org.RoleType(h.Cfg.AnonymousOrgRole)
reqContext.OrgID = orga.ID
reqContext.OrgName = orga.Name
return true
}
func (h *ContextHandler) getPrefixedAPIKey(ctx context.Context, keyString string) (*apikey.APIKey, error) {
// prefixed decode key
decoded, err := apikeygenprefix.Decode(keyString)
if err != nil {
return nil, err
}
hash, err := decoded.Hash()
if err != nil {
return nil, err
}
return h.apiKeyService.GetAPIKeyByHash(ctx, hash)
}
func (h *ContextHandler) getAPIKey(ctx context.Context, keyString string) (*apikey.APIKey, error) {
decoded, err := apikeygen.Decode(keyString)
if err != nil {
return nil, err
}
// fetch key
keyQuery := apikey.GetByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
if err := h.apiKeyService.GetApiKeyByName(ctx, &keyQuery); err != nil {
return nil, err
}
// validate api key
isValid, err := apikeygen.IsValid(decoded, keyQuery.Result.Key)
if err != nil {
return nil, err
}
if !isValid {
return nil, apikeygen.ErrInvalidApiKey
}
return keyQuery.Result, nil
}
func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bool {
header := reqContext.Req.Header.Get("Authorization")
parts := strings.SplitN(header, " ", 2)
var keyString string
if len(parts) == 2 && parts[0] == "Bearer" {
keyString = parts[1]
} else {
username, password, err := util.DecodeBasicAuthHeader(header)
if err == nil && username == "api_key" {
keyString = password
}
}
if keyString == "" {
return false
}
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAPIKey")
defer span.End()
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
*reqContext.Req = *reqContext.Req.WithContext(ctx)
var (
apikey *apikey.APIKey
errKey error
)
if strings.HasPrefix(keyString, apikeygenprefix.GrafanaPrefix) {
apikey, errKey = h.getPrefixedAPIKey(reqContext.Req.Context(), keyString) // decode prefixed key
} else {
apikey, errKey = h.getAPIKey(reqContext.Req.Context(), keyString) // decode legacy api key
}
if errKey != nil {
status := http.StatusInternalServerError
if errors.Is(errKey, apikeygen.ErrInvalidApiKey) {
status = http.StatusUnauthorized
}
reqContext.JsonApiErr(status, InvalidAPIKey, errKey)
return true
}
// check for expiration
getTime := h.GetTime
if getTime == nil {
getTime = time.Now
}
if apikey.Expires != nil && *apikey.Expires <= getTime().Unix() {
reqContext.JsonApiErr(http.StatusUnauthorized, "Expired API key", nil)
return true
}
if apikey.IsRevoked != nil && *apikey.IsRevoked {
reqContext.JsonApiErr(http.StatusUnauthorized, "Revoked token", nil)
return true
}
// update api_key last used date
if err := h.apiKeyService.UpdateAPIKeyLastUsedDate(reqContext.Req.Context(), apikey.Id); err != nil {
reqContext.JsonApiErr(http.StatusInternalServerError, InvalidAPIKey, errKey)
return true
}
if apikey.ServiceAccountId == nil || *apikey.ServiceAccountId < 1 { //There is no service account attached to the apikey
// Use the old APIkey method. This provides backwards compatibility.
// will probably have to be supported for a long time.
reqContext.SignedInUser = &user.SignedInUser{}
reqContext.OrgRole = apikey.Role
reqContext.ApiKeyID = apikey.Id
reqContext.OrgID = apikey.OrgId
reqContext.IsSignedIn = true
return true
}
//There is a service account attached to the API key
//Use service account linked to API key as the signed in user
querySignedInUser := user.GetSignedInUserQuery{UserID: *apikey.ServiceAccountId, OrgID: apikey.OrgId}
querySignedInUserResult, err := h.userService.GetSignedInUserWithCacheCtx(reqContext.Req.Context(), &querySignedInUser)
if err != nil {
reqContext.Logger.Error(
"Failed to link API key to service account in",
"id", querySignedInUser.UserID,
"org", querySignedInUser.OrgID,
"err", err,
)
reqContext.JsonApiErr(http.StatusInternalServerError, "Unable to link API key to service account", err)
return true
}
// disabled service accounts are not allowed to access the API
if querySignedInUserResult.IsDisabled {
reqContext.JsonApiErr(http.StatusUnauthorized, "Service account is disabled", nil)
return true
}
reqContext.IsSignedIn = true
reqContext.SignedInUser = querySignedInUserResult
return true
}
func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, orgID int64) bool {
if !h.Cfg.BasicAuthEnabled {
return false
}
header := reqContext.Req.Header.Get("Authorization")
if header == "" {
return false
}
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithBasicAuth")
defer span.End()
username, password, err := util.DecodeBasicAuthHeader(header)
if err != nil {
reqContext.JsonApiErr(401, "Invalid Basic Auth Header", err)
return true
}
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
*reqContext.Req = *reqContext.Req.WithContext(ctx)
authQuery := models.LoginUserQuery{
Username: username,
Password: password,
Cfg: h.Cfg,
}
if err := h.authenticator.AuthenticateUser(ctx, &authQuery); err != nil {
reqContext.Logger.Debug(
"Failed to authorize the user",
"username", username,
"err", err,
)
if errors.Is(err, user.ErrUserNotFound) {
err = login.ErrInvalidCredentials
}
reqContext.JsonApiErr(401, InvalidUsernamePassword, err)
return true
}
usr := authQuery.User
query := user.GetSignedInUserQuery{UserID: usr.ID, OrgID: orgID}
queryResult, err := h.userService.GetSignedInUserWithCacheCtx(ctx, &query)
if err != nil {
reqContext.Logger.Error(
"Failed at user signed in",
"id", usr.ID,
"org", orgID,
)
reqContext.JsonApiErr(401, InvalidUsernamePassword, err)
return true
}
reqContext.SignedInUser = queryResult
reqContext.IsSignedIn = true
return true
}
func (h *ContextHandler) initContextWithToken(reqContext *models.ReqContext, orgID int64) bool {
if h.Cfg.LoginCookieName == "" {
return false
}
rawToken := reqContext.GetCookie(h.Cfg.LoginCookieName)
if rawToken == "" {
return false
}
ctx, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithToken")
defer span.End()
token, err := h.AuthTokenService.LookupToken(ctx, rawToken)
if err != nil {
reqContext.Logger.Warn("failed to look up session from cookie", "error", err)
if errors.Is(err, auth.ErrUserTokenNotFound) || errors.Is(err, auth.ErrInvalidSessionToken) {
// Burn the cookie in case of invalid, expired or missing token
reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext))
}
reqContext.LookupTokenErr = err
return false
}
query := user.GetSignedInUserQuery{UserID: token.UserId, OrgID: orgID}
queryResult, err := h.userService.GetSignedInUserWithCacheCtx(ctx, &query)
if err != nil {
reqContext.Logger.Error("Failed to get user with id", "userId", token.UserId, "error", err)
return false
}
if h.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
// Check whether the logged in User has a token (whether the User used an OAuth provider to login)
oauthToken, exists, _ := h.oauthTokenService.HasOAuthEntry(ctx, queryResult)
if exists {
if h.hasAccessTokenExpired(oauthToken) {
reqContext.Logger.Info("access token expired", "userId", query.UserID, "expiry", fmt.Sprintf("%v", oauthToken.OAuthExpiry))
// If the User doesn't have a refresh_token or refreshing the token was unsuccessful then log out the User and invalidate the OAuth tokens
if err = h.oauthTokenService.TryTokenRefresh(ctx, oauthToken); err != nil {
if !errors.Is(err, oauthtoken.ErrNoRefreshTokenFound) {
reqContext.Logger.Error("could not fetch a new access token", "userId", oauthToken.UserId, "error", err)
}
reqContext.Resp.Before(h.deleteInvalidCookieEndOfRequestFunc(reqContext))
if err = h.oauthTokenService.InvalidateOAuthTokens(ctx, oauthToken); err != nil {
reqContext.Logger.Error("could not invalidate OAuth tokens", "userId", oauthToken.UserId, "error", err)
}
err = h.AuthTokenService.RevokeToken(ctx, token, false)
if err != nil && !errors.Is(err, auth.ErrUserTokenNotFound) {
reqContext.Logger.Error("failed to revoke auth token", "error", err)
}
return false
}
}
}
}
reqContext.SignedInUser = queryResult
reqContext.IsSignedIn = true
reqContext.UserToken = token
// Rotate the token just before we write response headers to ensure there is no delay between
// the new token being generated and the client receiving it.
reqContext.Resp.Before(h.rotateEndOfRequestFunc(reqContext, h.AuthTokenService, token))
return true
}
func (h *ContextHandler) deleteInvalidCookieEndOfRequestFunc(reqContext *models.ReqContext) web.BeforeFunc {
return func(w web.ResponseWriter) {
if w.Written() {
reqContext.Logger.Debug("Response written, skipping invalid cookie delete")
return
}
reqContext.Logger.Debug("Expiring invalid cookie")
cookies.DeleteCookie(reqContext.Resp, h.Cfg.LoginCookieName, nil)
}
}
func (h *ContextHandler) rotateEndOfRequestFunc(reqContext *models.ReqContext, authTokenService auth.UserTokenService,
token *auth.UserToken) web.BeforeFunc {
return func(w web.ResponseWriter) {
// if response has already been written, skip.
if w.Written() {
return
}
// if the request is cancelled by the client we should not try
// to rotate the token since the client would not accept any result.
if errors.Is(reqContext.Context.Req.Context().Err(), context.Canceled) {
return
}
ctx, span := h.tracer.Start(reqContext.Req.Context(), "rotateEndOfRequestFunc")
defer span.End()
addr := reqContext.RemoteAddr()
ip, err := network.GetIPFromAddress(addr)
if err != nil {
reqContext.Logger.Debug("Failed to get client IP address", "addr", addr, "err", err)
ip = nil
}
rotated, err := authTokenService.TryRotateToken(ctx, token, ip, reqContext.Req.UserAgent())
if err != nil {
reqContext.Logger.Error("Failed to rotate token", "error", err)
return
}
if rotated {
cookies.WriteSessionCookie(reqContext, h.Cfg, token.UnhashedToken, h.Cfg.LoginMaxLifetime)
}
}
}
func (h *ContextHandler) initContextWithRenderAuth(reqContext *models.ReqContext) bool {
key := reqContext.GetCookie("renderKey")
if key == "" {
return false
}
ctx, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithRenderAuth")
defer span.End()
renderUser, exists := h.RenderService.GetRenderUser(reqContext.Req.Context(), key)
if !exists {
reqContext.JsonApiErr(401, "Invalid Render Key", nil)
return true
}
reqContext.SignedInUser = &user.SignedInUser{
OrgID: renderUser.OrgID,
UserID: renderUser.UserID,
OrgRole: org.RoleType(renderUser.OrgRole),
}
// UserID can be 0 for background tasks and, in this case, there is no user info to retrieve
if renderUser.UserID != 0 {
query := user.GetSignedInUserQuery{UserID: renderUser.UserID, OrgID: renderUser.OrgID}
queryResult, err := h.userService.GetSignedInUserWithCacheCtx(ctx, &query)
if err == nil {
reqContext.SignedInUser = queryResult
}
}
reqContext.IsSignedIn = true
reqContext.IsRenderCall = true
reqContext.LastSeenAt = time.Now()
return true
}
func logUserIn(reqContext *models.ReqContext, auth *authproxy.AuthProxy, username string, logger log.Logger, ignoreCache bool) (int64, error) {
logger.Debug("Trying to log user in", "username", username, "ignoreCache", ignoreCache)
// Try to log in user via various providers
id, err := auth.Login(reqContext, ignoreCache)
if err != nil {
details := err
var e authproxy.Error
if errors.As(err, &e) {
details = e.DetailsError
}
logger.Error("Failed to login", "username", username, "message", err.Error(), "error", details,
"ignoreCache", ignoreCache)
return 0, err
}
return id, nil
}
func (h *ContextHandler) handleError(ctx *models.ReqContext, err error, statusCode int, cb func(error)) {
details := err
var e authproxy.Error
if errors.As(err, &e) {
details = e.DetailsError
}
ctx.Handle(h.Cfg, statusCode, err.Error(), details)
if cb != nil {
cb(details)
}
}
func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, orgID int64) bool {
username := reqContext.Req.Header.Get(h.Cfg.AuthProxyHeaderName)
logger := log.New("auth.proxy")
// Bail if auth proxy is not enabled
if !h.authProxy.IsEnabled() {
return false
}
// If there is no header - we can't move forward
if !h.authProxy.HasHeader(reqContext) {
return false
}
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAuthProxy")
defer span.End()
// Check if allowed continuing with this IP
if err := h.authProxy.IsAllowedIP(reqContext.Req.RemoteAddr); err != nil {
h.handleError(reqContext, err, 407, func(details error) {
logger.Error("Failed to check whitelisted IP addresses", "message", err.Error(), "error", details)
})
return true
}
id, err := logUserIn(reqContext, h.authProxy, username, logger, false)
if err != nil {
h.handleError(reqContext, err, 407, nil)
return true
}
logger.Debug("Got user ID, getting full user info", "userID", id)
user, err := h.authProxy.GetSignedInUser(id, orgID)
if err != nil {
// The reason we couldn't find the user corresponding to the ID might be that the ID was found from a stale
// cache entry. For example, if a user is deleted via the API, corresponding cache entries aren't invalidated
// because cache keys are computed from request header values and not just the user ID. Meaning that
// we can't easily derive cache keys to invalidate when deleting a user. To work around this, we try to
// log the user in again without the cache.
logger.Debug("Failed to get user info given ID, retrying without cache", "userID", id)
if err := h.authProxy.RemoveUserFromCache(reqContext); err != nil {
if !errors.Is(err, remotecache.ErrCacheItemNotFound) {
logger.Error("Got unexpected error when removing user from auth cache", "error", err)
}
}
id, err = logUserIn(reqContext, h.authProxy, username, logger, true)
if err != nil {
h.handleError(reqContext, err, 407, nil)
return true
}
user, err = h.authProxy.GetSignedInUser(id, orgID)
if err != nil {
h.handleError(reqContext, err, 407, nil)
return true
}
}
logger.Debug("Successfully got user info", "userID", user.UserID, "username", user.Login)
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), h.Cfg.AuthProxyHeaderName)
for _, header := range h.Cfg.AuthProxyHeaders {
if header != "" {
ctx = WithAuthHTTPHeader(ctx, header)
}
}
*reqContext.Req = *reqContext.Req.WithContext(ctx)
// Add user info to context
reqContext.SignedInUser = user
reqContext.IsSignedIn = true
// Remember user data in cache
if err := h.authProxy.Remember(reqContext, id); err != nil {
h.handleError(reqContext, err, 500, func(details error) {
logger.Error(
"Failed to store user in cache",
"username", username,
"message", err.Error(),
"error", details,
)
})
return true
}
return true
}
type authHTTPHeaderListContextKey struct{}
var authHTTPHeaderListKey = authHTTPHeaderListContextKey{}
// AuthHTTPHeaderList used to record HTTP headers that being when verifying authentication
// of an incoming HTTP request.
type AuthHTTPHeaderList struct {
Items []string
}
// WithAuthHTTPHeader returns a copy of parent in which the named HTTP header will be included
// and later retrievable by AuthHTTPHeaderListFromContext.
func WithAuthHTTPHeader(parent context.Context, name string) context.Context {
list := AuthHTTPHeaderListFromContext(parent)
if list == nil {
list = &AuthHTTPHeaderList{
Items: []string{},
}
}
list.Items = append(list.Items, name)
return context.WithValue(parent, authHTTPHeaderListKey, list)
}
// AuthHTTPHeaderListFromContext returns the AuthHTTPHeaderList in a context.Context, if any,
// and will include any HTTP headers used when verifying authentication of an incoming HTTP request.
func AuthHTTPHeaderListFromContext(c context.Context) *AuthHTTPHeaderList {
if list, ok := c.Value(authHTTPHeaderListKey).(*AuthHTTPHeaderList); ok {
return list
}
return nil
}
func (h *ContextHandler) hasAccessTokenExpired(token *models.UserAuth) bool {
if token.OAuthExpiry.IsZero() {
return false
}
getTime := h.GetTime
if getTime == nil {
getTime = time.Now
}
return token.OAuthExpiry.Round(0).Add(-oauthtoken.ExpiryDelta).Before(getTime())
}