...

Source file src/edge-infra.dev/pkg/edge/iam/barcode/flow_code.go

Documentation: edge-infra.dev/pkg/edge/iam/barcode

     1  package barcode
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/ory/fosite"
    10  	"github.com/ory/fosite/token/hmac"
    11  	"github.com/ory/x/errorsx"
    12  	"golang.org/x/crypto/bcrypt"
    13  
    14  	"edge-infra.dev/pkg/edge/iam/config"
    15  	iamErrors "edge-infra.dev/pkg/edge/iam/errors"
    16  	"edge-infra.dev/pkg/edge/iam/prometheus"
    17  	"edge-infra.dev/pkg/edge/iam/session"
    18  	"edge-infra.dev/pkg/edge/iam/util"
    19  )
    20  
    21  // CodeGrantHandler handles the "barcode_code" grant where you exchange the code
    22  // received on the printBarcodeUrl for an actual barcode
    23  type CodeGrantHandler struct {
    24  	BarcodeStrategy         *OpaqueStrategy
    25  	SignedStrategy          *SignedStrategy
    26  	BarcodeStorage          Storage
    27  	BarcodeCodeHMACStrategy *hmac.HMACStrategy
    28  
    29  	// Custom metrics
    30  	Metrics *prometheus.Metrics
    31  }
    32  
    33  // CanHandleTokenEndpointRequest makes sure we handle 'barcode_code' grant type
    34  func (h *CodeGrantHandler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
    35  	return requester.GetGrantTypes().ExactOne("barcode_code")
    36  }
    37  
    38  // CanSkipClientAuth makes sure we only allow authenticated clients
    39  func (h *CodeGrantHandler) CanSkipClientAuth(_ fosite.AccessRequester) bool {
    40  	return false
    41  }
    42  
    43  // HandleTokenEndpointRequest validates the code and tries to prevent any intrusion attempts
    44  func (h *CodeGrantHandler) HandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) error {
    45  	// only on grant type "barcode_code"
    46  	if !h.CanHandleTokenEndpointRequest(requester) {
    47  		return errorsx.WithStack(fosite.ErrUnknownRequest)
    48  	}
    49  	h.Metrics.IncHTTPRequestsTotal(signUpBarcode)
    50  
    51  	// get the client that made the request
    52  	client := requester.GetClient()
    53  
    54  	// make sure the client is allowed
    55  	if !client.GetGrantTypes().Has("barcode") {
    56  		return errorsx.WithStack(fosite.ErrUnauthorizedClient.
    57  			WithHint("The OAuth 2.0 Client is not allowed to use authorization grant 'barcode'."))
    58  	}
    59  
    60  	// and that auth is required
    61  	if client.IsPublic() {
    62  		return errorsx.WithStack(fosite.ErrInvalidGrant.
    63  			WithHint("The OAuth 2.0 Client is marked as public and is thus not allowed to use authorization grant 'barcode'."))
    64  	}
    65  
    66  	// take the code from the request
    67  	code := requester.GetRequestForm().Get("code")
    68  	signature := h.BarcodeCodeHMACStrategy.Signature(code)
    69  	// and try to get it from storage
    70  	codeData, err := h.BarcodeStorage.GetBarcodeCode(ctx, signature)
    71  	if err != nil {
    72  		// let's check if this error is because of used barcode code?
    73  		if errors.Is(err, iamErrors.ErrUsedBarcodeCode) {
    74  			return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint(err.Error()))
    75  		}
    76  		// this is internal server error
    77  		return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
    78  	}
    79  
    80  	// validate issued barcode-code with the signature and secret used to generate one.
    81  	err = h.BarcodeCodeHMACStrategy.Validate(code)
    82  	if err != nil {
    83  		return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error()))
    84  	}
    85  
    86  	// code is being exchanged after expiry
    87  	expires := codeData.CreatedAt.UTC().Add(config.GetBarcodeCodeLifespan())
    88  	if expires.Before(time.Now().UTC()) {
    89  		// do not continue and delete this `barcode code`, in the future it might be better
    90  		// to mark this code as used and if they try to use again we might
    91  		// have a tamper attempt. We can maybe delete active barcode or
    92  		// request additional verification on any next sign-in for that user
    93  		err := h.BarcodeStorage.DeleteBarcodeCode(ctx, code)
    94  		if err != nil {
    95  			return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
    96  		}
    97  
    98  		// error is NOT nil here!
    99  		return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("received expired/invalid code"))
   100  	}
   101  
   102  	// in populate token we create the barcode and we should mark the code as used
   103  	// if the code has been used already, revoke the tokens that belong to it
   104  	// https://github.com/ory/fosite/blob/7edf673f20aece260f9ba677a07086c48835fba8/handler/oauth2/flow_authorize_code_token.go#L59
   105  
   106  	// you need to be the client that asked for this code
   107  	if codeData.ClientID != client.GetID() {
   108  		// delete barcode code, we are compromised
   109  		err := h.BarcodeStorage.DeleteBarcodeCode(ctx, code)
   110  		if err != nil {
   111  			return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
   112  		}
   113  		return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("you are not the right client"))
   114  	}
   115  
   116  	// set the subject on the session for use in populate
   117  	subject := codeData.Subject
   118  	session.FromRequester(requester).SetSubject(subject)
   119  
   120  	return nil
   121  }
   122  
   123  func (h *CodeGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) (err error) {
   124  	// only on barcode grant
   125  	if !h.CanHandleTokenEndpointRequest(requester) {
   126  		return errorsx.WithStack(fosite.ErrUnknownRequest)
   127  	}
   128  	signature := h.BarcodeCodeHMACStrategy.Signature(requester.GetRequestForm().Get("code"))
   129  	barCodeCode, err := h.BarcodeStorage.GetBarcodeCode(ctx, signature)
   130  	if err != nil {
   131  		return errorsx.WithStack(fosite.ErrServerError.WithHint(err.Error()))
   132  	}
   133  	var barcode string
   134  	switch barCodeCode.Type {
   135  	case "qr":
   136  		barcode, err = h.SignedStrategy.Generate(barCodeCode.Subject, barCodeCode.IssuedBy)
   137  		if err != nil {
   138  			return errorsx.WithStack(fosite.ErrServerError.WithHint("failed to generate encoded token for QR barcode"))
   139  		}
   140  	case "128A":
   141  		barcode, err = h.BarcodeStrategy.Generate()
   142  		if err != nil {
   143  			return errorsx.WithStack(fosite.ErrServerError.WithHint("failed to generate encoded token for 128A barcode"))
   144  		}
   145  		subject := session.FromRequester(requester).GetSubject()
   146  		key := h.BarcodeStrategy.GetKey(barcode)
   147  		// Stores the barcode key, associating it with the challenge
   148  		if err = h.BarcodeStorage.CreateBarcodeKey(ctx, barCodeCode.Challenge, key); err != nil {
   149  			return errorsx.WithStack(fosite.ErrServerError.WithHintf("failed to create barcode key: %v", err.Error()))
   150  		}
   151  		// Creating a barcode user if it doesnt already exit.
   152  		// Or else we will get a 500 error trying to scan a barcode which didnt complete continuation properly
   153  		if _, err := h.BarcodeStorage.GetBarcodeUser(ctx, subject); err != nil {
   154  			if err = h.BarcodeStorage.CreateBarcodeUser(ctx, subject, ""); err != nil {
   155  				return errorsx.WithStack(fosite.ErrServerError.WithHintf("failed to create barcode user: %v", err.Error()))
   156  			}
   157  		}
   158  		barcodePrefixLen := uint8(len(config.BarcodePrefix())) /* #nosec G115 value from ENV and ENV is considered trusted */
   159  		secret := barcode[barcodePrefixLen+config.GetBarcodeKeyLength():]
   160  		hash, _ := bcrypt.GenerateFromPassword([]byte(secret), config.BcryptCost())
   161  
   162  		if err := h.BarcodeStorage.CreateBarcode(ctx, key, string(hash), subject); err != nil {
   163  			return fmt.Errorf("failed to create barcode 128A: %w", err)
   164  		}
   165  	}
   166  
   167  	// mark the barcode code as used
   168  	err = h.BarcodeStorage.InvalidateBarcodeCode(ctx, signature, barCodeCode.Subject, barCodeCode.Subject, barCodeCode.ClientID, barCodeCode.Type, barCodeCode.Challenge)
   169  	if err != nil {
   170  		return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()).WithHint("failed to invalidate barcode code"))
   171  	}
   172  
   173  	// return the barcode as access token with its own type
   174  	responder.SetAccessToken(barcode)
   175  	responder.SetTokenType(barCodeCode.Type)
   176  	h.Metrics.IncSignUpRequestsTotal(signUpBarcode, util.Succeeded)
   177  	return nil
   178  }
   179  

View as plain text