nuts-foundation/nuts-node

View on GitHub
auth/api/iam/validation.go

Summary

Maintainability
A
0 mins
Test Coverage
B
86%
/*
 * Copyright (C) 2023 Nuts community
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package iam

import (
    "context"
    "errors"
    "fmt"
    "github.com/nuts-foundation/go-did/did"
    "github.com/nuts-foundation/go-did/vc"
    "github.com/nuts-foundation/nuts-node/auth/oauth"
    "github.com/nuts-foundation/nuts-node/policy"
    "github.com/nuts-foundation/nuts-node/vcr/credential"
    "github.com/nuts-foundation/nuts-node/vcr/pe"
)

// validatePresentationSigner checks if the presenter of the VP is the same as the subject of the VCs being presented.
// All returned errors can be used as description in an OAuth2 error.
func validatePresentationSigner(presentation vc.VerifiablePresentation, expectedCredentialSubjectDID did.DID) (*did.DID, error) {
    if len(presentation.VerifiableCredential) == 0 {
        return credential.PresentationSigner(presentation)
    }
    subjectDID, err := credential.PresenterIsCredentialSubject(presentation)
    if err != nil {
        return nil, err
    }
    if subjectDID == nil {
        return nil, errors.New("presentation signer is not credential subject")
    }
    if !expectedCredentialSubjectDID.Empty() && !subjectDID.Equals(expectedCredentialSubjectDID) {
        return nil, errors.New("not all presentations have the same credential subject ID")
    }
    return subjectDID, nil
}

// validatePresentationAudience checks if the presentation audience (aud claim for JWTs, domain property for JSON-LD proofs) contains the issuer DID.
// it returns an OAuth2 error if the audience is missing or does not match the issuer.
func (r Wrapper) validatePresentationAudience(presentation vc.VerifiablePresentation, issuer did.DID) error {
    var audience []string
    switch presentation.Format() {
    case vc.JWTPresentationProofFormat:
        audience = presentation.JWT().Audience()
    case vc.JSONLDPresentationProofFormat:
        proof, err := credential.ParseLDProof(presentation)
        if err != nil {
            return err
        }
        if proof.Domain != nil {
            audience = []string{*proof.Domain}
        }
    }
    for _, aud := range audience {
        if aud == issuer.String() {
            return nil
        }
    }
    return oauth.OAuth2Error{
        Code:          oauth.InvalidRequest,
        Description:   "presentation audience/domain is missing or does not match",
        InternalError: fmt.Errorf("expected: %s, got: %v", issuer, audience),
    }
}

func (r Wrapper) presentationDefinitionForScope(ctx context.Context, authorizer did.DID, scope string) (pe.WalletOwnerMapping, error) {
    mapping, err := r.policyBackend.PresentationDefinitions(ctx, authorizer, scope)
    if err != nil {
        if errors.Is(err, policy.ErrNotFound) {
            return nil, oauth.OAuth2Error{
                Code:          oauth.InvalidScope,
                InternalError: err,
                Description:   fmt.Sprintf("unsupported scope (%s) for presentation exchange: %s", scope, err.Error()),
            }
        }
        return nil, oauth.OAuth2Error{
            Code:          oauth.ServerError,
            InternalError: err,
            Description:   fmt.Sprintf("failed to retrieve presentation definition for scope (%s): %s", scope, err.Error()),
        }
    }
    return mapping, err
}