package protocol import ( "fmt" "net/url" "regexp" "strings" ) const ( TokenExchangeGrantType = "urn:ietf:params:oauth:grant-type:token-exchange" JWTTokenType = "urn:ietf:params:oauth:token-type:jwt" AccessTokenType = "urn:ietf:params:oauth:token-type:access_token" ) var clientIDPattern = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$`) type ExchangeRequest struct { ClientID string SubjectToken string SubjectTokenType string RequestedTokenType string Audience string Scopes []string } type RequestError struct { Code string Description string } func (e *RequestError) Error() string { return fmt.Sprintf("%s: %s", e.Code, e.Description) } func ParseExchangeRequest(form url.Values) (ExchangeRequest, error) { request := ExchangeRequest{ ClientID: form.Get("client_id"), SubjectToken: form.Get("subject_token"), SubjectTokenType: form.Get("subject_token_type"), RequestedTokenType: form.Get("requested_token_type"), Audience: form.Get("audience"), Scopes: strings.Fields(form.Get("scope")), } if form.Get("grant_type") != TokenExchangeGrantType { return ExchangeRequest{}, requestError("unsupported_grant_type", "unsupported grant_type") } if !clientIDPattern.MatchString(request.ClientID) { return ExchangeRequest{}, requestError("invalid_request", "client_id must use 3-63 lowercase letters, digits, or interior hyphens") } if request.SubjectToken == "" { return ExchangeRequest{}, requestError("invalid_request", "subject_token is required") } if request.SubjectTokenType != JWTTokenType { return ExchangeRequest{}, requestError("invalid_request", "unsupported subject_token_type") } if request.RequestedTokenType != AccessTokenType { return ExchangeRequest{}, requestError("invalid_request", "unsupported requested_token_type") } if request.Audience == "" || len(form["audience"]) != 1 { return ExchangeRequest{}, requestError("invalid_target", "exactly one audience is required") } if len(request.Scopes) == 0 { return ExchangeRequest{}, requestError("invalid_scope", "scope is required") } if form.Has("resource") { return ExchangeRequest{}, requestError("invalid_target", "resource target is not supported") } for _, actorParameter := range []string{"actor_token", "actor_token_type"} { if form.Has(actorParameter) { return ExchangeRequest{}, requestError("invalid_request", "actor token is not supported") } } return request, nil } func requestError(code, description string) *RequestError { return &RequestError{Code: code, Description: description} }