build(deps): bump github.com/openshift-online/ocm-sdk-go

Bumps [github.com/openshift-online/ocm-sdk-go](https://github.com/openshift-online/ocm-sdk-go) from 0.1.204 to 0.1.208.
- [Release notes](https://github.com/openshift-online/ocm-sdk-go/releases)
- [Changelog](https://github.com/openshift-online/ocm-sdk-go/blob/master/CHANGES.adoc)
- [Commits](https://github.com/openshift-online/ocm-sdk-go/compare/v0.1.204...v0.1.208)

---
updated-dependencies:
- dependency-name: github.com/openshift-online/ocm-sdk-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2021-09-15 04:42:46 +00:00 committed by Ondřej Budai
parent aa08e29243
commit ebe3567aeb
9 changed files with 207 additions and 149 deletions

View file

@ -505,7 +505,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// Add the token to the context:
ctx = ContextWithToken(ctx, token)
ctx = ContextWithToken(ctx, token.object)
r = r.WithContext(ctx)
// Call the next handler:
@ -728,18 +728,22 @@ func (h *Handler) parseKey(data keyData) (key interface{}, err error) {
// checkToken checks if the token is valid. If it is valid it returns the parsed token, the
// claims and true. If it isn't valid it sends an error response to the client and returns false.
func (h *Handler) checkToken(w http.ResponseWriter, r *http.Request,
bearer string) (token *jwt.Token, claims jwt.MapClaims, ok bool) {
bearer string) (token *tokenInfo, claims jwt.MapClaims, ok bool) {
// Get the context:
ctx := r.Context()
// Parse the token:
claims = jwt.MapClaims{}
token, err := h.tokenParser.ParseWithClaims(
object, err := h.tokenParser.ParseWithClaims(
bearer, claims,
func(token *jwt.Token) (key interface{}, err error) {
return h.selectKey(ctx, token)
},
)
token = &tokenInfo{
text: bearer,
object: object,
}
if err != nil {
switch typed := err.(type) {
case *jwt.ValidationError:
@ -828,18 +832,26 @@ func (h *Handler) checkToken(w http.ResponseWriter, r *http.Request,
// something is wrong it sends an error response to the client and returns false.
func (h *Handler) checkClaims(w http.ResponseWriter, r *http.Request,
claims jwt.MapClaims) bool {
// Check the token type:
typ, ok := h.checkStringClaim(w, r, claims, "typ")
if !ok {
return false
}
if !strings.EqualFold(typ, "Bearer") {
h.sendError(
w, r,
"Bearer token type '%s' isn't supported",
typ,
)
return false
// The `typ` claim is optional, but if it exists the value must be `Bearer`:
value, ok := claims["typ"]
if ok {
typ, ok := value.(string)
if !ok {
h.sendError(
w, r,
"Bearer token type claim contains incorrect string value '%v'",
value,
)
return false
}
if !strings.EqualFold(typ, "Bearer") {
h.sendError(
w, r,
"Bearer token type '%s' isn't allowed",
typ,
)
return false
}
}
// Check the format of the issue and expiration date claims:
@ -853,7 +865,7 @@ func (h *Handler) checkClaims(w http.ResponseWriter, r *http.Request,
}
// Make sure that the impersonation flag claim doesn't exist, or is `false`:
value, ok := claims["impersonated"]
value, ok = claims["impersonated"]
if ok {
flag, ok := value.(bool)
if !ok {
@ -897,27 +909,6 @@ func (h *Handler) checkTimeClaim(w http.ResponseWriter, r *http.Request,
return
}
// checkStringClaim checks that the given claim exists and that the value is a string. If it doesn't
// exist or it has a wrong type it sends an error response to the client and returns false. If it
// exists it returns its value and true.
func (h *Handler) checkStringClaim(w http.ResponseWriter, r *http.Request,
claims jwt.MapClaims, name string) (result string, ok bool) {
value, ok := h.checkClaim(w, r, claims, name)
if !ok {
return
}
result, ok = value.(string)
if !ok {
h.sendError(
w, r,
"Bearer token claim '%s' contains incorrect text value '%v'",
name, value,
)
return
}
return
}
// checkClaim checks that the given claim exists. If it doesn't exist it sends an error response to
// the client and returns false. If it exists it returns its value and true.
func (h *Handler) checkClaim(w http.ResponseWriter, r *http.Request, claims jwt.MapClaims,

View file

@ -25,15 +25,22 @@ import (
"github.com/golang-jwt/jwt"
)
// tokenRemaining determines if the given token will eventually expire (offile access tokens, for
// example, never expire) and the time till it expires. That time will be positive if the token
// isn't expired, and negative if the token has already expired.
// tokenRemaining determines if the given token will eventually expire (offile access tokens and
// opaque tokens, for example, never expire) and the time till it expires. That time will be
// positive if the token isn't expired, and negative if the token has already expired.
//
// For tokens that don't have the `exp` claim, or that have it with value zero (typical for offline
// access tokens) the result will always be `false` and zero.
func tokenRemaining(token *jwt.Token, now time.Time) (expires bool, duration time.Duration,
func tokenRemaining(token *tokenInfo, now time.Time) (expires bool, duration time.Duration,
err error) {
claims, ok := token.Claims.(jwt.MapClaims)
// For opaque tokens we can't use the claims to determine when they expire, so we will
// assume that they never expire.
if token == nil || token.object == nil {
return
}
// For JSON web tokens we use tthe `exp` claim to determine when they expire.
claims, ok := token.object.Claims.(jwt.MapClaims)
if !ok {
err = fmt.Errorf("expected map claims but got %T", claims)
return

View file

@ -0,0 +1,27 @@
/*
Copyright (c) 2021 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authentication
import "github.com/golang-jwt/jwt"
// tokenInfo stores information about a token. We need to store both the original text and the
// parsed objects because some tokens (refresh tokens in particular) may be opaque strings instead
// of JSON web tokens.
type tokenInfo struct {
text string
object *jwt.Token
}

View file

@ -92,8 +92,8 @@ type TransportWrapper struct {
tokenServer *internal.ServerAddress
tokenMutex *sync.Mutex
tokenParser *jwt.Parser
accessToken *jwt.Token
refreshToken *jwt.Token
accessToken *tokenInfo
refreshToken *tokenInfo
// Fields used for metrics:
metricsSubsystem string
@ -337,39 +337,82 @@ func (b *TransportWrapperBuilder) Build(ctx context.Context) (result *TransportW
return
}
// Create the token parser:
tokenParser := &jwt.Parser{}
// Parse the tokens:
tokenParser := new(jwt.Parser)
var accessToken *jwt.Token
var refreshToken *jwt.Token
var accessToken *tokenInfo
var refreshToken *tokenInfo
for i, text := range b.tokens {
var token *jwt.Token
token, _, err = tokenParser.ParseUnverified(text, jwt.MapClaims{})
var object *jwt.Token
object, _, err = tokenParser.ParseUnverified(text, jwt.MapClaims{})
if err != nil {
err = fmt.Errorf("can't parse token %d: %w", i, err)
return
b.logger.Debug(
ctx,
"Can't parse token %d, will assume that it is an opaque "+
"refresh token: %v",
i, err,
)
refreshToken = &tokenInfo{
text: text,
}
continue
}
claims, ok := token.Claims.(jwt.MapClaims)
claims, ok := object.Claims.(jwt.MapClaims)
if !ok {
err = fmt.Errorf("claims of token %d are of type '%T'", i, claims)
return
}
claim, ok := claims["typ"]
if !ok {
err = fmt.Errorf("token %d doesn't contain the 'typ' claim", i)
return
// When the token doesn't have the `typ` claim we will use the position to
// decide: first token should be the access token and second should be the
// refresh token. That is consistent with the signature of the method that
// returns the tokens.
switch i {
case 0:
b.logger.Debug(
ctx,
"First token doesn't have a 'typ' claim, will assume "+
"that it is an access token",
)
accessToken = &tokenInfo{
text: text,
object: object,
}
continue
case 1:
b.logger.Debug(
ctx,
"Second token doesn't have a 'typ' claim, will assume "+
"that it is a refresh token",
)
refreshToken = &tokenInfo{
text: text,
object: object,
}
continue
default:
err = fmt.Errorf("token %d doesn't contain the 'typ' claim", i)
return
}
}
typ, ok := claim.(string)
if !ok {
err = fmt.Errorf("claim 'type' of token %d is of type '%T'", i, claim)
return
}
switch {
case strings.EqualFold(typ, "Bearer"):
accessToken = token
case strings.EqualFold(typ, "Refresh"):
refreshToken = token
case strings.EqualFold(typ, "Offline"):
refreshToken = token
switch strings.ToLower(typ) {
case "bearer":
accessToken = &tokenInfo{
text: text,
object: object,
}
case "refresh", "offline":
refreshToken = &tokenInfo{
text: text,
object: object,
}
default:
err = fmt.Errorf("type '%s' of token %d is unknown", typ, i)
return
@ -650,9 +693,9 @@ func (w *TransportWrapper) tokens(ctx context.Context, attempt int,
// Check the expiration times of the tokens:
now := time.Now()
var accessExpires bool
var accessReamining time.Duration
var accessRemaining time.Duration
if w.accessToken != nil {
accessExpires, accessReamining, err = tokenRemaining(w.accessToken, now)
accessExpires, accessRemaining, err = tokenRemaining(w.accessToken, now)
if err != nil {
return
}
@ -666,13 +709,13 @@ func (w *TransportWrapper) tokens(ctx context.Context, attempt int,
}
}
if w.logger.DebugEnabled() {
w.debugExpiry(ctx, "Bearer", w.accessToken, accessExpires, accessReamining)
w.debugExpiry(ctx, "Bearer", w.accessToken, accessExpires, accessRemaining)
w.debugExpiry(ctx, "Refresh", w.refreshToken, refreshExpires, refreshRemaining)
}
// If the access token is available and it isn't expired or about to expire then we can
// return the current tokens directly:
if w.accessToken != nil && (!accessExpires || accessReamining >= minRemaining) {
if w.accessToken != nil && (!accessExpires || accessRemaining >= minRemaining) {
access, refresh = w.currentTokens()
return
}
@ -739,12 +782,12 @@ func (w *TransportWrapper) tokens(ctx context.Context, attempt int,
// that the refresh token is unavailable or completely expired. And we know that we don't
// have credentials to request new tokens. But we can still use the access token if it isn't
// expired.
if w.accessToken != nil && accessReamining > 0 {
if w.accessToken != nil && accessRemaining > 0 {
w.logger.Warn(
ctx,
"Access token expires in only %s, but there is no other mechanism to "+
"obtain a new token, so will try to use it anyhow",
accessReamining,
accessRemaining,
)
access, refresh = w.currentTokens()
return
@ -764,10 +807,10 @@ func (w *TransportWrapper) tokens(ctx context.Context, attempt int,
// strings.
func (w *TransportWrapper) currentTokens() (access, refresh string) {
if w.accessToken != nil {
access = w.accessToken.Raw
access = w.accessToken.text
}
if w.refreshToken != nil {
refresh = w.refreshToken.Raw
refresh = w.refreshToken.text
}
return
}
@ -801,7 +844,7 @@ func (w *TransportWrapper) sendRefreshForm(ctx context.Context, attempt int) (co
form := url.Values{}
form.Set(grantTypeField, refreshTokenGrant)
form.Set(clientIDField, w.clientID)
form.Set(refreshTokenField, w.refreshToken.Raw)
form.Set(refreshTokenField, w.refreshToken.text)
code, result, err = w.sendForm(ctx, form, attempt)
return
}
@ -901,37 +944,64 @@ func (w *TransportWrapper) sendFormTimed(ctx context.Context, form url.Values) (
err = fmt.Errorf("token response status code is '%d'", response.StatusCode)
return
}
if result.TokenType != nil && *result.TokenType != "bearer" {
err = fmt.Errorf("expected 'bearer' token type but got '%s", *result.TokenType)
if result.TokenType != nil && !strings.EqualFold(*result.TokenType, "bearer") {
err = fmt.Errorf("expected 'bearer' token type but got '%s'", *result.TokenType)
return
}
// The response should always contains the access token, regardless of the kind of grant
// that was used:
var accessToken *jwt.Token
var accessTokenText string
var accessTokenObject *jwt.Token
var accessToken *tokenInfo
if result.AccessToken == nil {
err = fmt.Errorf("no access token was received")
return
}
accessToken, _, err = w.tokenParser.ParseUnverified(*result.AccessToken, jwt.MapClaims{})
accessTokenText = *result.AccessToken
accessTokenObject, _, err = w.tokenParser.ParseUnverified(
accessTokenText,
jwt.MapClaims{},
)
if err != nil {
return
}
if accessTokenText != "" {
accessToken = &tokenInfo{
text: accessTokenText,
object: accessTokenObject,
}
}
// The refresh token isn't mandatory for the client credentials grant:
var refreshToken *jwt.Token
// The refresh token isn't mandatory for the password and client credentials grants:
var refreshTokenText string
var refreshTokenObject *jwt.Token
var refreshToken *tokenInfo
if result.RefreshToken == nil {
if form.Get(grantTypeField) != clientCredentialsGrant {
grantType := form.Get(grantTypeField)
if grantType != passwordGrant && grantType != clientCredentialsGrant {
err = fmt.Errorf("no refresh token was received")
return
}
} else {
refreshToken, _, err = w.tokenParser.ParseUnverified(
*result.RefreshToken,
refreshTokenText = *result.RefreshToken
refreshTokenObject, _, err = w.tokenParser.ParseUnverified(
refreshTokenText,
jwt.MapClaims{},
)
if err != nil {
return
w.logger.Debug(
ctx,
"Refresh token can't be parsed, will assume it is opaque: %v",
err,
)
err = nil
}
}
if refreshTokenText != "" {
refreshToken = &tokenInfo{
text: refreshTokenText,
object: refreshTokenObject,
}
}
@ -946,12 +1016,6 @@ func (w *TransportWrapper) sendFormTimed(ctx context.Context, form url.Values) (
return
}
// haveCredentials returns true if the connection has credentials that can be used to request new
// tokens.
func (w *TransportWrapper) haveCredentials() bool {
return w.havePassword() || w.haveSecret()
}
func (w *TransportWrapper) havePassword() bool {
return w.user != "" && w.password != ""
}
@ -961,8 +1025,8 @@ func (w *TransportWrapper) haveSecret() bool {
}
// debugExpiry sends to the log information about the expiration of the given token.
func (w *TransportWrapper) debugExpiry(ctx context.Context, typ string, token *jwt.Token, expires bool,
left time.Duration) {
func (w *TransportWrapper) debugExpiry(ctx context.Context, typ string, token *tokenInfo,
expires bool, left time.Duration) {
if token != nil {
if expires {
if left < 0 {

View file

@ -349,7 +349,7 @@ func (s *ClientSelector) createTransport(ctx context.Context,
dialer := net.Dialer{}
return dialer.DialContext(ctx, UnixNetwork, address.Socket)
}
transport.DialTLS = func(_, _ string) (net.Conn, error) {
transport.DialTLS = func(_, _ string) (net.Conn, error) { // nolint
// TODO: This ignores the passed context because it isn't currently
// supported. Once we migrate to Go 1.15 it should be done like
// this:

View file

@ -120,7 +120,7 @@ func (l *GoLogger) Debug(ctx context.Context, format string, args ...interface{}
if l.debugEnabled {
msg := fmt.Sprintf(format, args...)
// #nosec G104
log.Output(1, msg)
_ = log.Output(1, msg)
}
}
@ -130,7 +130,7 @@ func (l *GoLogger) Info(ctx context.Context, format string, args ...interface{})
if l.infoEnabled {
msg := fmt.Sprintf(format, args...)
// #nosec G104
log.Output(1, msg)
_ = log.Output(1, msg)
}
}
@ -140,7 +140,7 @@ func (l *GoLogger) Warn(ctx context.Context, format string, args ...interface{})
if l.warnEnabled {
msg := fmt.Sprintf(format, args...)
// #nosec G104
log.Output(1, msg)
_ = log.Output(1, msg)
}
}
@ -150,7 +150,7 @@ func (l *GoLogger) Error(ctx context.Context, format string, args ...interface{}
if l.errorEnabled {
msg := fmt.Sprintf(format, args...)
// #nosec G104
log.Output(1, msg)
_ = log.Output(1, msg)
}
}
@ -160,6 +160,6 @@ func (l *GoLogger) Error(ctx context.Context, format string, args ...interface{}
func (l *GoLogger) Fatal(ctx context.Context, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
// #nosec G104
log.Output(1, msg)
_ = log.Output(1, msg)
os.Exit(1)
}