...

Source file src/github.com/ory/fosite/introspection_request_handler.go

Documentation: github.com/ory/fosite

     1  /*
     2   * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
    17   * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
    18   * @license 	Apache-2.0
    19   *
    20   */
    21  
    22  package fosite
    23  
    24  import (
    25  	"context"
    26  	"net/http"
    27  	"net/url"
    28  	"strings"
    29  
    30  	"github.com/ory/x/errorsx"
    31  	"golang.org/x/text/language"
    32  )
    33  
    34  // NewIntrospectionRequest initiates token introspection as defined in
    35  // https://tools.ietf.org/search/rfc7662#section-2.1
    36  //
    37  // The protected resource calls the introspection endpoint using an HTTP
    38  // POST [RFC7231] request with parameters sent as
    39  // "application/x-www-form-urlencoded" data as defined in
    40  // [W3C.REC-html5-20141028].  The protected resource sends a parameter
    41  // representing the token along with optional parameters representing
    42  // additional context that is known by the protected resource to aid the
    43  // authorization server in its response.
    44  //
    45  // * token
    46  // REQUIRED.  The string value of the token.  For access tokens, this
    47  // is the "access_token" value returned from the token endpoint
    48  // defined in OAuth 2.0 [RFC6749], Section 5.1.  For refresh tokens,
    49  // this is the "refresh_token" value returned from the token endpoint
    50  // as defined in OAuth 2.0 [RFC6749], Section 5.1.  Other token types
    51  // are outside the scope of this specification.
    52  //
    53  // * token_type_hint
    54  // OPTIONAL.  A hint about the type of the token submitted for
    55  // introspection.  The protected resource MAY pass this parameter to
    56  // help the authorization server optimize the token lookup.  If the
    57  // server is unable to locate the token using the given hint, it MUST
    58  // extend its search across all of its supported token types.  An
    59  // authorization server MAY ignore this parameter, particularly if it
    60  // is able to detect the token type automatically.  Values for this
    61  // field are defined in the "OAuth Token Type Hints" registry defined
    62  // in OAuth Token Revocation [RFC7009].
    63  //
    64  // The introspection endpoint MAY accept other OPTIONAL parameters to
    65  // provide further context to the query.  For instance, an authorization
    66  // server may desire to know the IP address of the client accessing the
    67  // protected resource to determine if the correct client is likely to be
    68  // presenting the token.  The definition of this or any other parameters
    69  // are outside the scope of this specification, to be defined by service
    70  // documentation or extensions to this specification.  If the
    71  // authorization server is unable to determine the state of the token
    72  // without additional information, it SHOULD return an introspection
    73  // response indicating the token is not active as described in
    74  // Section 2.2.
    75  //
    76  // To prevent token scanning attacks, the endpoint MUST also require
    77  // some form of authorization to access this endpoint, such as client
    78  // authentication as described in OAuth 2.0 [RFC6749] or a separate
    79  // OAuth 2.0 access token such as the bearer token described in OAuth
    80  // 2.0 Bearer Token Usage [RFC6750].  The methods of managing and
    81  // validating these authentication credentials are out of scope of this
    82  // specification.
    83  //
    84  // For example, the following shows a protected resource calling the
    85  // token introspection endpoint to query about an OAuth 2.0 bearer
    86  // token.  The protected resource is using a separate OAuth 2.0 bearer
    87  // token to authorize this call.
    88  //
    89  // The following is a non-normative example request:
    90  //
    91  //	POST /introspect HTTP/1.1
    92  //	Host: server.example.com
    93  //	Accept: application/json
    94  //	Content-Type: application/x-www-form-urlencoded
    95  //	Authorization: Bearer 23410913-abewfq.123483
    96  //
    97  //	token=2YotnFZFEjr1zCsicMWpAA
    98  //
    99  // In this example, the protected resource uses a client identifier and
   100  // client secret to authenticate itself to the introspection endpoint.
   101  // The protected resource also sends a token type hint indicating that
   102  // it is inquiring about an access token.
   103  //
   104  // The following is a non-normative example request:
   105  //
   106  //	POST /introspect HTTP/1.1
   107  //	Host: server.example.com
   108  //	Accept: application/json
   109  //	Content-Type: application/x-www-form-urlencoded
   110  //	Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
   111  //
   112  //	token=mF_9.B5f-4.1JqM&token_type_hint=access_token
   113  func (f *Fosite) NewIntrospectionRequest(ctx context.Context, r *http.Request, session Session) (IntrospectionResponder, error) {
   114  	ctx = context.WithValue(ctx, RequestContextKey, r)
   115  
   116  	if r.Method != "POST" {
   117  		return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHintf("HTTP method is '%s' but expected 'POST'.", r.Method))
   118  	} else if err := r.ParseMultipartForm(1 << 20); err != nil && err != http.ErrNotMultipart {
   119  		return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHint("Unable to parse HTTP body, make sure to send a properly formatted form request body.").WithWrap(err).WithDebug(err.Error()))
   120  	} else if len(r.PostForm) == 0 {
   121  		return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInvalidRequest.WithHint("The POST body can not be empty."))
   122  	}
   123  
   124  	token := r.PostForm.Get("token")
   125  	tokenTypeHint := r.PostForm.Get("token_type_hint")
   126  	scope := r.PostForm.Get("scope")
   127  	if clientToken := AccessTokenFromRequest(r); clientToken != "" {
   128  		if token == clientToken {
   129  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Bearer and introspection token are identical."))
   130  		}
   131  
   132  		if tu, _, err := f.IntrospectToken(ctx, clientToken, AccessToken, session.Clone()); err != nil {
   133  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing, malformed, or credentials used are invalid."))
   134  		} else if tu != "" && tu != AccessToken {
   135  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHintf("HTTP Authorization header did not provide a token of type 'access_token', got type '%s'.", tu))
   136  		}
   137  	} else {
   138  		id, secret, ok := r.BasicAuth()
   139  		if !ok {
   140  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("HTTP Authorization header missing."))
   141  		}
   142  
   143  		clientID, err := url.QueryUnescape(id)
   144  		if err != nil {
   145  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client ID from HTTP basic authorization header, make sure it is properly encoded.").WithWrap(err).WithDebug(err.Error()))
   146  		}
   147  
   148  		clientSecret, err := url.QueryUnescape(secret)
   149  		if err != nil {
   150  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to decode OAuth 2.0 Client Secret from HTTP basic authorization header, make sure it is properly encoded.").WithWrap(err).WithDebug(err.Error()))
   151  		}
   152  
   153  		client, err := f.Store.GetClient(ctx, clientID)
   154  		if err != nil {
   155  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("Unable to find OAuth 2.0 Client from HTTP basic authorization header.").WithWrap(err).WithDebug(err.Error()))
   156  		}
   157  
   158  		// Enforce client authentication
   159  		if err := f.checkClientSecret(ctx, client, []byte(clientSecret)); err != nil {
   160  			return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrRequestUnauthorized.WithHint("OAuth 2.0 Client credentials are invalid."))
   161  		}
   162  	}
   163  
   164  	tu, ar, err := f.IntrospectToken(ctx, token, TokenUse(tokenTypeHint), session, RemoveEmpty(strings.Split(scope, " "))...)
   165  	if err != nil {
   166  		return &IntrospectionResponse{Active: false}, errorsx.WithStack(ErrInactiveToken.WithHint("An introspection strategy indicated that the token is inactive.").WithWrap(err).WithDebug(err.Error()))
   167  	}
   168  	accessTokenType := ""
   169  
   170  	if tu == AccessToken {
   171  		accessTokenType = BearerAccessToken
   172  	}
   173  
   174  	return &IntrospectionResponse{
   175  		Active:          true,
   176  		AccessRequester: ar,
   177  		TokenUse:        tu,
   178  		AccessTokenType: accessTokenType,
   179  	}, nil
   180  }
   181  
   182  type IntrospectionResponse struct {
   183  	Active          bool            `json:"active"`
   184  	AccessRequester AccessRequester `json:"extra"`
   185  	TokenUse        TokenUse        `json:"token_use,omitempty"`
   186  	AccessTokenType string          `json:"token_type,omitempty"`
   187  	Lang            language.Tag    `json:"-"`
   188  }
   189  
   190  func (r *IntrospectionResponse) IsActive() bool {
   191  	return r.Active
   192  }
   193  
   194  func (r *IntrospectionResponse) GetAccessRequester() AccessRequester {
   195  	return r.AccessRequester
   196  }
   197  
   198  func (r *IntrospectionResponse) GetTokenUse() TokenUse {
   199  	return r.TokenUse
   200  }
   201  
   202  func (r *IntrospectionResponse) GetAccessTokenType() string {
   203  	return r.AccessTokenType
   204  }
   205  

View as plain text