Skip to content

Commit

Permalink
Merge pull request #334 from privacybydesign/client-integration-test
Browse files Browse the repository at this point in the history
Test: add irmaclient integration test
  • Loading branch information
ivard authored Sep 5, 2023
2 parents 82d5c7f + f24cac0 commit 163b005
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 36 deletions.
77 changes: 77 additions & 0 deletions .github/actions/integration-test/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Integration test
description: Runs the tests in ./internal/sessiontest/client_integration_test.go against the given IRMA server and keyshare server artifacts.
inputs:
test-ref:
description: The branch, tag or SHA to check out the tests from
required: true
irma-server-artifact:
description: Artifact url or id of the irma server artifact to use
required: true
keyshare-server-artifact:
description: Artifact url or id of the keyshare server artifact to use
required: true
runs:
using: composite
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ^1.18

- name: Download IRMA server artifact (url)
if: startsWith(inputs.irma-server-artifact, 'https://')
run: curl --create-dirs -L -o ./bin-is/irma-linux-amd64 ${{ inputs.irma-server-artifact }}
shell: bash

- name: Download IRMA server artifact (artifact id)
if: ${{ !startsWith(inputs.irma-server-artifact, 'https://') }}
uses: actions/download-artifact@v2
with:
name: ${{ inputs.irma-server-artifact }}
path: bin-is

- name: Set file permissions for bin-is
run: chmod +x ./bin-is/irma-linux-amd64
shell: bash

- name: Download keyshare server artifact (url)
if: startsWith(inputs.keyshare-server-artifact, 'https://')
run: curl --create-dirs -L -o ./bin-ks/irma-linux-amd64 ${{ inputs.keyshare-server-artifact }}
shell: bash

- name: Download keyshare server artifact (artifact id)
if: ${{ !startsWith(inputs.keyshare-server-artifact, 'https://') }}
uses: actions/download-artifact@v2
with:
name: ${{ inputs.keyshare-server-artifact }}
path: bin-ks

- name: Set file permissions for bin-ks
run: chmod +x ./bin-ks/irma-linux-amd64
shell: bash

- name: Run keyshare server utilities
run: docker-compose up -d
shell: bash

# We add & at the end of each command to run them in the background.
- name: Run IRMA server
run: ./bin-is/irma-linux-amd64 server -s testdata/irma_configuration --url http://localhost:port -p 48682 -k testdata/privatekeys &
shell: bash

- name: Run keyshare server
run: ./bin-ks/irma-linux-amd64 keyshare server -c testdata/configurations/keyshareserver.yml &
shell: bash

- name: Checkout test code
uses: actions/checkout@v3
with:
ref: ${{ inputs.test-ref }}
path: irmago_test_checkout

- name: Run integration tests
working-directory: irmago_test_checkout
env:
IRMAGO_INTEGRATION_TESTS: Y
run: go test -v -run TestClientIntegration -p 1 ./...
shell: bash
44 changes: 44 additions & 0 deletions .github/workflows/status-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ jobs:

- name: Build artifact
uses: ./.github/actions/build
id: build
with:
os: ${{ matrix.os }}
arch: ${{ matrix.arch }}

- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: irma-${{ matrix.os }}-${{ matrix.arch }}
path: ${{ steps.build.outputs.artifact-name }}

docker-build:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -89,6 +96,43 @@ jobs:
- name: Run all unit tests
run: docker-compose run test -v ./...

# The integration tests are split into two jobs, one for the client side and one for the server side.
# They test whether irmago versions with different versions of gabi can interact.
# We assume that the keyshare server is always kept up to date, so we don't test using older versions of the keyshare server.
integration-test-clientside: # Checks whether irmaclient interacts with older IRMA servers
needs: build
strategy:
matrix:
irma-server:
- v0.13.2
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Run integration test
uses: ./.github/actions/integration-test
with:
test-ref: ${{ github.ref }}
irma-server-artifact: https://github.com/privacybydesign/irmago/releases/download/${{ matrix.irma-server }}/irma-linux-amd64
keyshare-server-artifact: irma-linux-amd64 # Current build

integration-test-serverside: # Checks whether IRMA server interacts with older irmaclients
needs: build
strategy:
matrix:
irmaclient-ref:
- ab3f743c339f10bd80fe79d0d8d34e8be0c430a3 # v0.13.2 (integration test did not exist when version tag was created)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Run integration test
uses: ./.github/actions/integration-test
with:
test-ref: ${{ matrix.irmaclient-ref }}
irma-server-artifact: irma-linux-amd64 # Current build
keyshare-server-artifact: irma-linux-amd64 # Current build

analyze:
needs: build
runs-on: ubuntu-latest
Expand Down
76 changes: 76 additions & 0 deletions internal/sessiontest/client_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package sessiontest

/*
This file contains the integration tests for the irmaclient library.
A subset of tests from session_test.go and keyshare_test.go can be run against specific versions of the IRMA server and keyshare server.
In this way we test the backwards compatibility of the irmaclient library.
The other way around, the backwards compatibility of the IRMA server and keyshare server, can be tested by checking out the
source code of an older irmago version and run an older version of this test against a newer server version.
This integration test is being introduced after irmago v0.13.2, so older irmaclient versions cannot be tested using this setup.
This test only runs if you pass IRMAGO_INTEGRATION_TESTS=Y to go test, i.e.: IRMAGO_INTEGRATION_TESTS=Y go test -run TestClientIntegration -p 1 ./...
Before running this test, you should start the IRMA server and keyshare server manually.
First, ensure you installed the desired irma version.
To start the IRMA server, run the following command:
$ irma server -s testdata/irma_configuration --url http://localhost:port -p 48682 -k testdata/privatekeys
To start the keyshare server, run the following commands:
$ docker-compose up -d
$ irma keyshare server -c testdata/configurations/keyshareserver.yml
*/

import (
"os"
"strings"
"testing"

irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/test"
)

func TestClientIntegration(t *testing.T) {
if !strings.HasPrefix(strings.ToUpper(os.Getenv("IRMAGO_INTEGRATION_TESTS")), "Y") {
t.Skip("set IRMAGO_INTEGRATION_TESTS=Y to run this test")
}

// Tests without keyshare server.
t.Run("DisclosureSession", apply(testDisclosureSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("NoAttributeDisclosureSession", apply(testNoAttributeDisclosureSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("EmptyDisclosure", apply(testEmptyDisclosure, nil, optionReuseServer, optionForceNoAuth))
t.Run("SigningSession", apply(testSigningSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceSession", apply(testIssuanceSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("MultipleIssuanceSession", apply(testMultipleIssuanceSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("DefaultCredentialValidity", apply(testDefaultCredentialValidity, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceDisclosureEmptyAttributes", apply(testIssuanceDisclosureEmptyAttributes, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceOptionalZeroLengthAttributes", apply(testIssuanceOptionalZeroLengthAttributes, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceOptionalSetAttributes", apply(testIssuanceOptionalSetAttributes, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceSameAttributesNotSingleton", apply(testIssuanceSameAttributesNotSingleton, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuancePairing", apply(testIssuancePairing, nil, optionReuseServer, optionForceNoAuth))
t.Run("PairingRejected", apply(testPairingRejected, nil, optionReuseServer, optionForceNoAuth))
t.Run("LargeAttribute", apply(testLargeAttribute, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuanceSingletonCredential", apply(testIssuanceSingletonCredential, nil, optionReuseServer, optionForceNoAuth))
t.Run("UnsatisfiableDisclosureSession", apply(testUnsatisfiableDisclosureSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("AttributeByteEncoding", apply(testAttributeByteEncoding, nil, optionReuseServer, optionForceNoAuth))
t.Run("IssuedCredentialIsStored", apply(testIssuedCredentialIsStored, nil, optionReuseServer, optionForceNoAuth))
t.Run("BlindIssuanceSession", apply(testBlindIssuanceSession, nil, optionReuseServer, optionForceNoAuth))
t.Run("DisablePairing", apply(testDisablePairing, nil, optionReuseServer, optionForceNoAuth))
t.Run("DisclosureMultipleAttrs", apply(testDisclosureMultipleAttrs, nil, optionReuseServer, optionForceNoAuth))
t.Run("CombinedSessionMultipleAttributes", apply(testCombinedSessionMultipleAttributes, nil, optionReuseServer, optionForceNoAuth))
t.Run("ConDisCon", apply(testConDisCon, nil, optionReuseServer, optionForceNoAuth))
t.Run("OptionalDisclosure", apply(testOptionalDisclosure, nil, optionReuseServer, optionForceNoAuth))

// Test with keyshare server.
t.Run("KeyshareSessions", func(t *testing.T) {
storage := test.CreateTestStorage(t)
client, handler := parseExistingStorage(t, storage)
defer test.ClearTestStorage(t, client, handler.storage)

// Fresh irmaclient storage was used, so we need to do some initialization.
client.KeyshareEnroll(irma.NewSchemeManagerIdentifier("test"), nil, "12345", "en")
req := getIssuanceRequest(false)
doSession(t, req, client, nil, nil, nil, nil, optionReuseServer, optionForceNoAuth)

keyshareSessions(t, client, nil, optionReuseServer, optionForceNoAuth)
})
}
71 changes: 40 additions & 31 deletions internal/sessiontest/helper_dosession_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
optionRetryPost
optionIgnoreError
optionReuseServer // makes doSession assume a requestor server with authentication is used
optionForceNoAuth // makes doSession assume no authentication is required (useful when reused server has no authentication)
optionClientWait
optionWait
optionPrePairingClient
Expand Down Expand Up @@ -104,7 +105,7 @@ func startServer(t *testing.T, opts option, irmaServer *IrmaServer, conf interfa

// startSessionAtServer starts an IRMA session using the specified session request, against an IRMA server
// or library, as determined by the type of serv.
func startSessionAtServer(t *testing.T, serv stopper, conf interface{}, request interface{}) *server.SessionPackage {
func startSessionAtServer(t *testing.T, serv stopper, useJWTs bool, request interface{}) *server.SessionPackage {
switch s := serv.(type) {
case *IrmaServer:
qr, requestorToken, frontendRequest, err := s.irma.StartSession(request, nil)
Expand All @@ -116,15 +117,9 @@ func startSessionAtServer(t *testing.T, serv stopper, conf interface{}, request
}
default:
var (
sesPkg server.SessionPackage
err error
useJWTs bool
sesPkg server.SessionPackage
err error
)
if conf != nil {
useJWTs = !conf.(*requestorserver.Configuration).DisableRequestorAuthentication
} else {
useJWTs = true
}
url := requestorServerURL
if useJWTs {
skbts, err := os.ReadFile(filepath.Join(testdata, "jwtkeys", "requestor1-sk.pem"))
Expand Down Expand Up @@ -153,7 +148,7 @@ func startSessionAtClient(t *testing.T, sesPkg *server.SessionPackage, client *i
}

// getSessionResult retrieves the session result from the IRMA server or library.
func getSessionResult(t *testing.T, sesPkg *server.SessionPackage, serv stopper, opts option) *server.SessionResult {
func getSessionResult(t *testing.T, sesPkg *server.SessionPackage, serv stopper, useJWTs bool, opts option) *server.SessionResult {
waitSessionFinished(t, serv, sesPkg.Token, opts.enabled(optionWait))

switch s := serv.(type) {
Expand All @@ -162,28 +157,35 @@ func getSessionResult(t *testing.T, sesPkg *server.SessionPackage, serv stopper,
require.NoError(t, err)
return result
default:
var res string
err := irma.NewHTTPTransport(requestorServerURL+"/session/"+string(sesPkg.Token), false).Get("result-jwt", &res)
require.NoError(t, err)
if useJWTs {
var res string
err := irma.NewHTTPTransport(requestorServerURL+"/session/"+string(sesPkg.Token), false).Get("result-jwt", &res)
require.NoError(t, err)

bts, err := os.ReadFile(jwtPrivkeyPath)
require.NoError(t, err)
sk, err := jwt.ParseRSAPrivateKeyFromPEM(bts)
require.NoError(t, err)
bts, err := os.ReadFile(jwtPrivkeyPath)
require.NoError(t, err)
sk, err := jwt.ParseRSAPrivateKeyFromPEM(bts)
require.NoError(t, err)

// Validate JWT
claims := struct {
jwt.RegisteredClaims
*server.SessionResult
}{}
_, err = jwt.ParseWithClaims(res, &claims, func(_ *jwt.Token) (interface{}, error) {
return &sk.PublicKey, nil
})
require.NoError(t, err)
// Validate JWT
claims := struct {
jwt.RegisteredClaims
*server.SessionResult
}{}
_, err = jwt.ParseWithClaims(res, &claims, func(_ *jwt.Token) (interface{}, error) {
return &sk.PublicKey, nil
})
require.NoError(t, err)

// Check default expiration time
require.True(t, claims.IssuedAt.Add(irma.DefaultJwtValidity*time.Second).Equal(claims.ExpiresAt.Time))
return claims.SessionResult
// Check default expiration time
require.True(t, claims.IssuedAt.Add(irma.DefaultJwtValidity*time.Second).Equal(claims.ExpiresAt.Time))
return claims.SessionResult
} else {
var res server.SessionResult
err := irma.NewHTTPTransport(requestorServerURL+"/session/"+string(sesPkg.Token), false).Get("result", &res)
require.NoError(t, err)
return &res
}
}
}

Expand Down Expand Up @@ -264,7 +266,14 @@ func doSession(
defer serv.Stop()
}

sesPkg := startSessionAtServer(t, serv, conf, request)
useJWTs := true
if opts.enabled(optionForceNoAuth) {
useJWTs = false
} else if rconf, ok := conf.(*requestorserver.Configuration); ok {
useJWTs = !rconf.DisableRequestorAuthentication
}

sesPkg := startSessionAtServer(t, serv, useJWTs, request)
sessionHandler, clientChan := createSessionHandler(t, opts, client, sesPkg, frontendOptionsHandler, pairingHandler)

if frontendOptionsHandler != nil {
Expand Down Expand Up @@ -293,7 +302,7 @@ func doSession(
return &requestorSessionResult{nil, nil, clientResult.Missing, dismisser}
}

serverResult := getSessionResult(t, sesPkg, serv, opts)
serverResult := getSessionResult(t, sesPkg, serv, useJWTs, opts)
require.Equal(t, sesPkg.Token, serverResult.Token)

if opts.enabled(optionRetryPost) {
Expand Down
8 changes: 4 additions & 4 deletions internal/sessiontest/keyshare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestKeyshareSessions(t *testing.T) {
keyshareSessions(t, client, irmaServer)
}

func keyshareSessions(t *testing.T, client *irmaclient.Client, irmaServer *IrmaServer) {
func keyshareSessions(t *testing.T, client *irmaclient.Client, irmaServer *IrmaServer, options ...option) {
id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
expiry := irma.Timestamp(irma.NewMetadataAttribute(0).Expiry())
issuanceRequest := getCombinedIssuanceRequest(id)
Expand All @@ -73,15 +73,15 @@ func keyshareSessions(t *testing.T, client *irmaclient.Client, irmaServer *IrmaS
Attributes: map[string]string{"email": "testusername"},
},
)
doSession(t, issuanceRequest, client, irmaServer, nil, nil, nil)
doSession(t, issuanceRequest, client, irmaServer, nil, nil, nil, options...)

disclosureRequest := getDisclosureRequest(id)
disclosureRequest.AddSingle(irma.NewAttributeTypeIdentifier("test.test.mijnirma.email"), nil, nil)
doSession(t, disclosureRequest, client, irmaServer, nil, nil, nil)
doSession(t, disclosureRequest, client, irmaServer, nil, nil, nil, options...)

sigRequest := getSigningRequest(id)
sigRequest.AddSingle(irma.NewAttributeTypeIdentifier("test.test.mijnirma.email"), nil, nil)
doSession(t, sigRequest, client, irmaServer, nil, nil, nil)
doSession(t, sigRequest, client, irmaServer, nil, nil, nil, options...)
}

func TestIssuanceCombinedMultiSchemeSession(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion internal/sessiontest/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,8 @@ func TestStatusEventsSSE(t *testing.T) {

// Start a session at the server
request := irma.NewDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
sesPkg := startSessionAtServer(t, rs, conf, request)
useJWTs := !conf.DisableRequestorAuthentication
sesPkg := startSessionAtServer(t, rs, useJWTs, request)

// Start SSE connections to the SSE endpoints
url := fmt.Sprintf("http://localhost:%d/session/%s/statusevents", conf.Port, sesPkg.Token)
Expand Down

0 comments on commit 163b005

Please sign in to comment.