nuts-foundation/nuts-auth

View on GitHub
pkg/services/dummy/dummy.go

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * Nuts auth
 * Copyright (C) 2020. 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 dummy

import (
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "errors"
    "time"

    "github.com/nuts-foundation/nuts-auth/pkg/contract"
    "github.com/nuts-foundation/nuts-auth/pkg/services"
)

// ContractFormat is the contract format type
const ContractFormat = contract.SigningMeans("dummy")

// VerifiablePresentationType is the dummy verifiable presentation type
const VerifiablePresentationType = contract.VPType("DummyVerifiablePresentation")

// NoSignatureType is a VerifiablePresentation Proof type where no signature is given
const NoSignatureType = "NoSignature"

// SessionCreated represents the session state after creation
const SessionCreated = "created"

// SessionInProgress represents the session state after the first SessionStatus call
const SessionInProgress = "in-progress"

// SessionCompleted represents the session state after the second SessionStatus call
const SessionCompleted = "completed"

var errNotEnabled = errors.New("not allowed in strict mode")

// Dummy is a contract signer and verifier that always succeeds unless you try to use it in strict mode
// The dummy signer is not supposed to be used in a clustered context unless consecutive calls arrive at the same instance
type Dummy struct {
    InStrictMode bool
    Sessions     map[string]string
    Status       map[string]string
}

// Presentation is a VerifiablePresentation without valid cryptographic proofs
// It is only usable in non-strict mode.
type Presentation struct {
    contract.VerifiablePresentationBase
    Proof Proof
}

// Proof holds the Proof generated from the dummy Signer
type Proof struct {
    // Proof type, mandatory
    Type string
    // Contract as how it was presented to the user
    Contract string
    // Initials form the signing means
    Initials string
    // Lastname form the signing means
    Lastname string
    // Birthdate form the signing means
    Birthdate string
    // Email form the signing means
    Email string
}

// SignedToken is the Dummy implementation of a Signed token.
// It can be used in the dummy.Service service.
type SignedToken struct {
    signerAttributes map[string]string
    contract         contract.Contract
}

// SignerAttributes returns the attributes used to sign the token
func (d SignedToken) SignerAttributes() (map[string]string, error) {
    return d.signerAttributes, nil
}

// Contract returns the contract
func (d SignedToken) Contract() contract.Contract {
    return d.contract
}

// sessionPointer contains a information to facilitate session discoverability for the signing means
type sessionPointer struct {
    sessionID string
}

// SessionID returns a string which can be used by the signing means to find the session
func (s sessionPointer) SessionID() string {
    return s.sessionID
}

// Payload returns always the dummy value
func (s sessionPointer) Payload() []byte {
    return []byte("dummy")
}

func (s sessionPointer) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        SessionID string `json:"sessionID"`
    }{SessionID: s.sessionID})
}

type signingSessionResult struct {
    ID      string
    State   string
    Request string
}

// Status returns the current state of the signing session
func (d signingSessionResult) Status() string {
    return d.State
}

// VerifiablePresentation returns the contract.VerifiablePresentation if the session is completed, nil otherwise.
func (d signingSessionResult) VerifiablePresentation() (contract.VerifiablePresentation, error) {
    // todo: the contract template should be used to select the dummy attributes to add

    if d.Status() != SessionCompleted {
        return nil, nil
    }

    return Presentation{
        VerifiablePresentationBase: contract.VerifiablePresentationBase{
            Context: []string{contract.VerifiableCredentialContext},
            Type:    []contract.VPType{contract.VerifiablePresentationType, VerifiablePresentationType},
        },
        Proof: Proof{
            Type:      NoSignatureType,
            Initials:  "I",
            Lastname:  "Tester",
            Birthdate: "1980-01-01",
            Email:     "tester@example.com",
            Contract:  d.Request,
        },
    }, nil
}

// VerifyVP check a Dummy VerifiablePresentation. It Returns a verificationResult if all was fine, an error otherwise.
func (d Dummy) VerifyVP(rawVerifiablePresentation []byte, checkTime *time.Time) (*contract.VPVerificationResult, error) {
    if d.InStrictMode {
        return nil, errNotEnabled
    }

    p := Presentation{}
    if err := json.Unmarshal(rawVerifiablePresentation, &p); err != nil {
        return nil, err
    }

    c, err := contract.ParseContractString(p.Proof.Contract, contract.StandardContractTemplates)
    if err != nil {
        return nil, err
    }

    return &contract.VPVerificationResult{
        Validity: contract.Valid,
        VPType:   VerifiablePresentationType,
        DisclosedAttributes: map[string]string{
            "initials":  p.Proof.Initials,
            "lastname":  p.Proof.Lastname,
            "birthdate": p.Proof.Birthdate,
            "email":     p.Proof.Initials,
        },
        ContractAttributes: c.Params,
    }, nil
}

// SigningSessionStatus looks up the session by the provided sessionID param.
// When the session exists it returns the current state and advances the state to the next one.
// When the session is SessionComplete, it removes the session from the sessionStore.
func (d Dummy) SigningSessionStatus(sessionID string) (contract.SigningSessionResult, error) {
    if d.InStrictMode {
        return nil, errNotEnabled
    }

    state, ok := d.Status[sessionID]
    if !ok {
        return nil, services.ErrSessionNotFound
    }

    session, ok := d.Sessions[sessionID]
    if !ok {
        return nil, services.ErrSessionNotFound
    }

    // increase session status everytime this request is made
    switch state {
    case SessionCreated:
        d.Status[sessionID] = SessionInProgress
    case SessionInProgress:
        d.Status[sessionID] = SessionCompleted
    case SessionCompleted:
        delete(d.Status, sessionID)
        delete(d.Sessions, sessionID)
    }
    return signingSessionResult{
        ID:      sessionID,
        State:   state,
        Request: session,
    }, nil
}

// StartSigningSession starts a Dummy session. It takes any string and stores it under a random sessionID.
// This method is not available in strictMode
// returns the sessionPointer with the sessionID
func (d Dummy) StartSigningSession(rawContractText string) (contract.SessionPointer, error) {
    if d.InStrictMode {
        return nil, errNotEnabled
    }
    sessionBytes := make([]byte, 16)
    rand.Reader.Read(sessionBytes)

    sessionID := hex.EncodeToString(sessionBytes)
    d.Status[sessionID] = SessionCreated
    d.Sessions[sessionID] = rawContractText

    return sessionPointer{
        sessionID: sessionID,
    }, nil
}