...

Source file src/github.com/ory/fosite/introspection_response_writer.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  	"encoding/json"
    26  	"net/http"
    27  	"strings"
    28  
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // WriteIntrospectionError responds with token metadata discovered by token introspection as defined in
    33  // https://tools.ietf.org/search/rfc7662#section-2.2
    34  //
    35  // If the protected resource uses OAuth 2.0 client credentials to
    36  // authenticate to the introspection endpoint and its credentials are
    37  // invalid, the authorization server responds with an HTTP 401
    38  // (Unauthorized) as described in Section 5.2 of OAuth 2.0 [RFC6749].
    39  //
    40  // If the protected resource uses an OAuth 2.0 bearer token to authorize
    41  // its call to the introspection endpoint and the token used for
    42  // authorization does not contain sufficient privileges or is otherwise
    43  // invalid for this request, the authorization server responds with an
    44  // HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
    45  // Usage [RFC6750].
    46  //
    47  // Note that a properly formed and authorized query for an inactive or
    48  // otherwise invalid token (or a token the protected resource is not
    49  // allowed to know about) is not considered an error response by this
    50  // specification.  In these cases, the authorization server MUST instead
    51  // respond with an introspection response with the "active" field set to
    52  // "false" as described in Section 2.2.
    53  func (f *Fosite) WriteIntrospectionError(rw http.ResponseWriter, err error) {
    54  	if err == nil {
    55  		return
    56  	}
    57  
    58  	// Inactive token errors should never written out as an error.
    59  	if !errors.Is(err, ErrInactiveToken) && (errors.Is(err, ErrInvalidRequest) || errors.Is(err, ErrRequestUnauthorized)) {
    60  		f.writeJsonError(rw, nil, err)
    61  		return
    62  	}
    63  
    64  	rw.Header().Set("Content-Type", "application/json;charset=UTF-8")
    65  	rw.Header().Set("Cache-Control", "no-store")
    66  	rw.Header().Set("Pragma", "no-cache")
    67  	_ = json.NewEncoder(rw).Encode(struct {
    68  		Active bool `json:"active"`
    69  	}{Active: false})
    70  }
    71  
    72  // WriteIntrospectionResponse responds with an error if token introspection failed as defined in
    73  // https://tools.ietf.org/search/rfc7662#section-2.3
    74  //
    75  // The server responds with a JSON object [RFC7159] in "application/
    76  // json" format with the following top-level members.
    77  //
    78  // * active
    79  // REQUIRED.  Boolean indicator of whether or not the presented token
    80  // is currently active.  The specifics of a token's "active" state
    81  // will vary depending on the implementation of the authorization
    82  // server and the information it keeps about its tokens, but a "true"
    83  // value return for the "active" property will generally indicate
    84  // that a given token has been issued by this authorization server,
    85  // has not been revoked by the resource owner, and is within its
    86  // given time window of validity (e.g., after its issuance time and
    87  // before its expiration time).  See Section 4 for information on
    88  // implementation of such checks.
    89  //
    90  // * scope
    91  // OPTIONAL.  A JSON string containing a space-separated list of
    92  // scopes associated with this token, in the format described in
    93  // Section 3.3 of OAuth 2.0 [RFC6749].
    94  //
    95  // * client_id
    96  // OPTIONAL.  Client identifier for the OAuth 2.0 client that
    97  // requested this token.
    98  //
    99  // * username
   100  // OPTIONAL.  Human-readable identifier for the resource owner who
   101  // authorized this token.
   102  //
   103  // * token_type
   104  // OPTIONAL.  Type of the token as defined in Section 5.1 of OAuth
   105  // 2.0 [RFC6749].
   106  //
   107  // * exp
   108  // OPTIONAL.  Integer timestamp, measured in the number of seconds
   109  // since January 1 1970 UTC, indicating when this token will expire,
   110  // as defined in JWT [RFC7519].
   111  //
   112  // * iat
   113  // OPTIONAL.  Integer timestamp, measured in the number of seconds
   114  // since January 1 1970 UTC, indicating when this token was
   115  // originally issued, as defined in JWT [RFC7519].
   116  //
   117  // * nbf
   118  // OPTIONAL.  Integer timestamp, measured in the number of seconds
   119  // since January 1 1970 UTC, indicating when this token is not to be
   120  // used before, as defined in JWT [RFC7519].
   121  //
   122  // * sub
   123  // OPTIONAL.  Subject of the token, as defined in JWT [RFC7519].
   124  // Usually a machine-readable identifier of the resource owner who
   125  // authorized this token.
   126  //
   127  // * aud
   128  // OPTIONAL.  Service-specific string identifier or list of string
   129  // identifiers representing the intended audience for this token, as
   130  // defined in JWT [RFC7519].
   131  //
   132  // * iss
   133  // OPTIONAL.  String representing the issuer of this token, as
   134  // defined in JWT [RFC7519].
   135  //
   136  // * jti
   137  // OPTIONAL.  String identifier for the token, as defined in JWT
   138  // [RFC7519].
   139  //
   140  // Specific implementations MAY extend this structure with their own
   141  // service-specific response names as top-level members of this JSON
   142  // object.  Response names intended to be used across domains MUST be
   143  // registered in the "OAuth Token Introspection Response" registry
   144  // defined in Section 3.1.
   145  //
   146  // The authorization server MAY respond differently to different
   147  // protected resources making the same request.  For instance, an
   148  // authorization server MAY limit which scopes from a given token are
   149  // returned for each protected resource to prevent a protected resource
   150  // from learning more about the larger network than is necessary for its
   151  // operation.
   152  //
   153  // The response MAY be cached by the protected resource to improve
   154  // performance and reduce load on the introspection endpoint, but at the
   155  // cost of liveness of the information used by the protected resource to
   156  // make authorization decisions.  See Section 4 for more information
   157  // regarding the trade off when the response is cached.
   158  //
   159  //
   160  // For example, the following response contains a set of information
   161  // about an active token:
   162  //
   163  // The following is a non-normative example response:
   164  //
   165  //	 HTTP/1.1 200 OK
   166  //	 Content-Type: application/json
   167  //
   168  //	 {
   169  //	   "active": true,
   170  //	   "client_id": "l238j323ds-23ij4",
   171  //	   "username": "jdoe",
   172  //	   "scope": "read write dolphin",
   173  //	   "sub": "Z5O3upPC88QrAjx00dis",
   174  //	   "aud": "https://protected.example.net/resource",
   175  //	   "iss": "https://server.example.com/",
   176  //	   "exp": 1419356238,
   177  //	   "iat": 1419350238,
   178  //	   "extension_field": "twenty-seven"
   179  //	 }
   180  //
   181  // If the introspection call is properly authorized but the token is not
   182  // active, does not exist on this server, or the protected resource is
   183  // not allowed to introspect this particular token, then the
   184  // authorization server MUST return an introspection response with the
   185  // "active" field set to "false".  Note that to avoid disclosing too
   186  // much of the authorization server's state to a third party, the
   187  // authorization server SHOULD NOT include any additional information
   188  // about an inactive token, including why the token is inactive.
   189  //
   190  // The following is a non-normative example response for a token that
   191  // has been revoked or is otherwise invalid:
   192  //
   193  //	 HTTP/1.1 200 OK
   194  //	 Content-Type: application/json
   195  //
   196  //	 {
   197  //	   "active": false
   198  //	 }
   199  func (f *Fosite) WriteIntrospectionResponse(rw http.ResponseWriter, r IntrospectionResponder) {
   200  	if !r.IsActive() {
   201  		_ = json.NewEncoder(rw).Encode(&struct {
   202  			Active bool `json:"active"`
   203  		}{Active: false})
   204  		return
   205  	}
   206  
   207  	response := map[string]interface{}{
   208  		"active": true,
   209  	}
   210  
   211  	extraClaimsSession, ok := r.GetAccessRequester().GetSession().(ExtraClaimsSession)
   212  	if ok {
   213  		extraClaims := extraClaimsSession.GetExtraClaims()
   214  		if extraClaims != nil {
   215  			for name, value := range extraClaims {
   216  				switch name {
   217  				// We do not allow these to be set through extra claims.
   218  				case "exp", "client_id", "scope", "iat", "sub", "aud", "username":
   219  					continue
   220  				default:
   221  					response[name] = value
   222  				}
   223  			}
   224  		}
   225  	}
   226  
   227  	if !r.GetAccessRequester().GetSession().GetExpiresAt(AccessToken).IsZero() {
   228  		response["exp"] = r.GetAccessRequester().GetSession().GetExpiresAt(AccessToken).Unix()
   229  	}
   230  	if r.GetAccessRequester().GetClient().GetID() != "" {
   231  		response["client_id"] = r.GetAccessRequester().GetClient().GetID()
   232  	}
   233  	if len(r.GetAccessRequester().GetGrantedScopes()) > 0 {
   234  		response["scope"] = strings.Join(r.GetAccessRequester().GetGrantedScopes(), " ")
   235  	}
   236  	if !r.GetAccessRequester().GetRequestedAt().IsZero() {
   237  		response["iat"] = r.GetAccessRequester().GetRequestedAt().Unix()
   238  	}
   239  	if r.GetAccessRequester().GetSession().GetSubject() != "" {
   240  		response["sub"] = r.GetAccessRequester().GetSession().GetSubject()
   241  	}
   242  	if len(r.GetAccessRequester().GetGrantedAudience()) > 0 {
   243  		response["aud"] = r.GetAccessRequester().GetGrantedAudience()
   244  	}
   245  	if r.GetAccessRequester().GetSession().GetUsername() != "" {
   246  		response["username"] = r.GetAccessRequester().GetSession().GetUsername()
   247  	}
   248  
   249  	rw.Header().Set("Content-Type", "application/json;charset=UTF-8")
   250  	rw.Header().Set("Cache-Control", "no-store")
   251  	rw.Header().Set("Pragma", "no-cache")
   252  	_ = json.NewEncoder(rw).Encode(response)
   253  }
   254  

View as plain text