Compare commits
205 Commits
v0.25.3-rc
...
master
Author | SHA1 | Date |
---|---|---|
github-actions[bot] | a85723d9f8 | 4 days ago |
Max | c3beeaf44c | 4 days ago |
github-actions[bot] | 367d90d0cc | 4 days ago |
dependabot[bot] | 27bea359cb | 4 days ago |
github-actions[bot] | 5eb1849a8f | 4 days ago |
Max | ee69818c27 | 4 days ago |
github-actions[bot] | 7ff52f6e6c | 4 days ago |
Carl Tashian | 12c2e75db6 | 4 days ago |
dependabot[bot] | d12d866f84 | 5 days ago |
dependabot[bot] | 43bf6b5a46 | 5 days ago |
dependabot[bot] | fffffc60a8 | 5 days ago |
github-actions[bot] | 9bf6a8369f | 2 weeks ago |
dependabot[bot] | 43bdd61803 | 2 weeks ago |
github-actions[bot] | 1563c264bd | 2 weeks ago |
github-actions[bot] | 14230d8696 | 2 weeks ago |
dependabot[bot] | 958c344f18 | 2 weeks ago |
dependabot[bot] | 8a7892463d | 2 weeks ago |
Max | 890e81cb66 | 2 weeks ago |
github-actions[bot] | 2d4bc95460 | 2 weeks ago |
Max | 93ca1e21c1 | 2 weeks ago |
Max | d6973c979e | 2 weeks ago |
Max | d4b2916650 | 2 weeks ago |
Mariano Cano | f9e5971978 | 2 weeks ago |
Mariano Cano | c8e65abfb1 | 2 weeks ago |
Mariano Cano | b4616ee8f1 | 2 weeks ago |
Mariano Cano | 634ece4489 | 2 weeks ago |
Mariano Cano | a017c0e3fb | 2 weeks ago |
dependabot[bot] | 7bc9d15849 | 3 weeks ago |
github-actions[bot] | 8b36f7bc11 | 3 weeks ago |
Max | 30b2cd1e5c | 3 weeks ago |
github-actions[bot] | a0b9360725 | 3 weeks ago |
Max | d5171be998 | 3 weeks ago |
github-actions[bot] | 6e12cfa46c | 3 weeks ago |
Max | d1de1ad8df | 3 weeks ago |
Max | 0ce8fb6db5 | 3 weeks ago |
Max | 3b9631b883 | 3 weeks ago |
Max | 474f5d28f4 | 3 weeks ago |
dependabot[bot] | 7ab8391070 | 3 weeks ago |
dependabot[bot] | 23f120e9db | 3 weeks ago |
dependabot[bot] | e3444c0736 | 3 weeks ago |
github-actions[bot] | 669d992d59 | 4 weeks ago |
github-actions[bot] | 68c5238fe7 | 4 weeks ago |
github-actions[bot] | 4884379710 | 4 weeks ago |
dependabot[bot] | 437154d70e | 4 weeks ago |
dependabot[bot] | 2a9bbff8ce | 4 weeks ago |
dependabot[bot] | 4d7ca9d672 | 4 weeks ago |
github-actions[bot] | 587d0d5a27 | 1 month ago |
dependabot[bot] | 34fde59927 | 1 month ago |
github-actions[bot] | fe8c3d3ee8 | 1 month ago |
dependabot[bot] | 013c2f2f04 | 1 month ago |
github-actions[bot] | 4208b0a63f | 1 month ago |
github-actions[bot] | 6de7aa97dc | 1 month ago |
dependabot[bot] | f3e4f0ae64 | 1 month ago |
dependabot[bot] | 2b8f3e7080 | 1 month ago |
Mariano Cano | 47b5048d82 | 1 month ago |
github-actions[bot] | 7d6eea0ff0 | 1 month ago |
max furman | 99ce13a4ea | 1 month ago |
dependabot[bot] | 5cdfc2c972 | 1 month ago |
github-actions[bot] | 980687bc0e | 1 month ago |
dependabot[bot] | 8121a05c55 | 1 month ago |
Mariano Cano | ad0ac55b41 | 1 month ago |
Mariano Cano | 192e90eea7 | 1 month ago |
Mariano Cano | 812ffd3c40 | 1 month ago |
Mariano Cano | d0548f9ec9 | 1 month ago |
Mariano Cano | 14959dbb2e | 1 month ago |
Mariano Cano | c0b7c33a58 | 1 month ago |
Mariano Cano | 9e8087fbb1 | 1 month ago |
Mariano Cano | 8673818980 | 1 month ago |
Mariano Cano | f3f484cee2 | 1 month ago |
Mariano Cano | fdb0cf03f6 | 1 month ago |
Mariano Cano | d4862a2520 | 1 month ago |
Mariano Cano | e08b27708b | 1 month ago |
Mariano Cano | b6afed3be7 | 1 month ago |
github-actions[bot] | 9355923799 | 2 months ago |
dependabot[bot] | a8e9a18bcc | 2 months ago |
github-actions[bot] | 803d3d3981 | 2 months ago |
github-actions[bot] | e0e7ae6c89 | 2 months ago |
github-actions[bot] | 72a8bb3d39 | 2 months ago |
Mariano Cano | 5fa5a63de8 | 2 months ago |
dependabot[bot] | 9cbdc73809 | 2 months ago |
dependabot[bot] | 42341c7a39 | 2 months ago |
dependabot[bot] | 0dff5c4b84 | 2 months ago |
github-actions[bot] | e3ba702811 | 2 months ago |
github-actions[bot] | fe29ccaee3 | 2 months ago |
github-actions[bot] | 8cf5e3c2c6 | 2 months ago |
dependabot[bot] | 928d446e93 | 2 months ago |
dependabot[bot] | e11833ef7a | 2 months ago |
github-actions[bot] | 591b9f743d | 2 months ago |
Mariano Cano | a2f2332848 | 2 months ago |
Mariano Cano | b1e31b1726 | 2 months ago |
Mariano Cano | cca6f6d01b | 2 months ago |
Mariano Cano | d037ed6ff2 | 2 months ago |
dependabot[bot] | 9b2566556e | 2 months ago |
dependabot[bot] | 8933a2e514 | 2 months ago |
github-actions[bot] | 2c71543d33 | 2 months ago |
Herman Slatman | 949e2fdb1c | 2 months ago |
dependabot[bot] | 281efbb949 | 2 months ago |
github-actions[bot] | 14b1211e7a | 2 months ago |
github-actions[bot] | 0b894a0d4a | 2 months ago |
Mariano Cano | 20e315bac0 | 2 months ago |
Mariano Cano | 296ac4e207 | 2 months ago |
github-actions[bot] | 28a87bbaa6 | 2 months ago |
dependabot[bot] | bf03d56ae4 | 2 months ago |
dependabot[bot] | 6715c653ca | 2 months ago |
dependabot[bot] | 798e190621 | 2 months ago |
findnature | 5072d7a58f | 2 months ago |
Max | 9cbab5a5b3 | 2 months ago |
Jeremy Doupe | 03c3cf5790 | 2 months ago |
Herman Slatman | d6bf551f87 | 2 months ago |
github-actions[bot] | f4d506f659 | 2 months ago |
Herman Slatman | 1e5e267b2b | 2 months ago |
max furman | 760014c64b | 2 months ago |
Herman Slatman | 2561a7271e | 2 months ago |
dependabot[bot] | 39653050dc | 2 months ago |
github-actions[bot] | 65cfee56df | 2 months ago |
dependabot[bot] | 8d4effcce8 | 2 months ago |
github-actions[bot] | 4a375592d3 | 2 months ago |
github-actions[bot] | d7ed03124f | 2 months ago |
Herman Slatman | 87202001a8 | 2 months ago |
dependabot[bot] | 57a6b85388 | 2 months ago |
dependabot[bot] | 0ba61c58e8 | 2 months ago |
Herman Slatman | b0fabe1346 | 2 months ago |
Herman Slatman | 113a6dd8ab | 2 months ago |
Herman Slatman | 6bc0a86207 | 2 months ago |
Jeremy Doupe | aa543a335a | 2 months ago |
Jeremy Doupe | 4879376138 | 2 months ago |
Jeremy Doupe | 2fcf34066b | 2 months ago |
Herman Slatman | 07279dd248 | 2 months ago |
Herman Slatman | 4c6b0b318e | 2 months ago |
Herman Slatman | f1a2c68f43 | 2 months ago |
Mariano Cano | 7df3ad05ed | 3 months ago |
Mariano Cano | 4202d6673c | 3 months ago |
Herman Slatman | d6bbe5b06b | 3 months ago |
Herman Slatman | 721345eea6 | 3 months ago |
verytrap | db92404342 | 3 months ago |
Mariano Cano | 725a913f66 | 3 months ago |
Herman Slatman | 397877a7b6 | 3 months ago |
Herman Slatman | b226b6eb4c | 3 months ago |
Herman Slatman | 02956ad0e3 | 3 months ago |
Herman Slatman | 037554e774 | 3 months ago |
github-actions[bot] | 1513152cb2 | 3 months ago |
dependabot[bot] | c9ba31ae61 | 3 months ago |
github-actions[bot] | 1f69ff84a1 | 3 months ago |
dependabot[bot] | a76f07143b | 3 months ago |
github-actions[bot] | 08ef9fe245 | 3 months ago |
dependabot[bot] | 57d628513b | 3 months ago |
github-actions[bot] | d5758ba3a3 | 3 months ago |
github-actions[bot] | 166c4968cf | 3 months ago |
Carl Tashian | 1be0932a0f | 3 months ago |
Carl Tashian | f04a5e39c4 | 3 months ago |
dependabot[bot] | d1523c93bc | 3 months ago |
dependabot[bot] | 44c48a7494 | 3 months ago |
Carl Tashian | 188e4e3ff3 | 3 months ago |
Max | 395a3eeb93 | 3 months ago |
Herman Slatman | 4772d7cc28 | 3 months ago |
Herman Slatman | 854288a0cb | 3 months ago |
github-actions[bot] | 4016b69b28 | 3 months ago |
github-actions[bot] | b5b723e835 | 3 months ago |
github-actions[bot] | 0a6e79a745 | 3 months ago |
dependabot[bot] | 9d86361ae3 | 3 months ago |
github-actions[bot] | 7e053437b1 | 3 months ago |
dependabot[bot] | 014b4ef2c0 | 3 months ago |
dependabot[bot] | 21734f7742 | 3 months ago |
dependabot[bot] | 927cd97bd5 | 3 months ago |
Herman Slatman | 2650944d65 | 3 months ago |
Herman Slatman | 7888d868ba | 3 months ago |
Joe Doss | 56c4f3bdd5 | 3 months ago |
Joe Doss | 14c9de2570 | 3 months ago |
github-actions[bot] | 44f44e34e4 | 3 months ago |
github-actions[bot] | 73c827c7fa | 3 months ago |
dependabot[bot] | 9874214669 | 3 months ago |
github-actions[bot] | c5a13d86ba | 3 months ago |
github-actions[bot] | 70085fcfeb | 3 months ago |
dependabot[bot] | 1a768ad522 | 3 months ago |
dependabot[bot] | 4dfade10b8 | 3 months ago |
dependabot[bot] | 9a75f93250 | 3 months ago |
Mariano Cano | fe364d5081 | 4 months ago |
Mariano Cano | 0ac9023590 | 4 months ago |
Mariano Cano | 10f6a901ec | 4 months ago |
Herman Slatman | ef1631b00d | 4 months ago |
Herman Slatman | 6204a1441e | 4 months ago |
github-actions[bot] | d03700b155 | 4 months ago |
github-actions[bot] | 1362d4dea6 | 4 months ago |
dependabot[bot] | cc7ac97c51 | 4 months ago |
dependabot[bot] | 22781b8460 | 4 months ago |
github-actions[bot] | 9033120401 | 4 months ago |
github-actions[bot] | 87506d738c | 4 months ago |
dependabot[bot] | 4940556234 | 4 months ago |
dependabot[bot] | 299102955b | 4 months ago |
Herman Slatman | 5254702130 | 4 months ago |
Herman Slatman | b7a6b93717 | 4 months ago |
github-actions[bot] | 9b1dfcf5d0 | 4 months ago |
dependabot[bot] | 9327859f55 | 4 months ago |
Carl Tashian | 26b7761a9c | 4 months ago |
Herman Slatman | b8510dd5b2 | 4 months ago |
Herman Slatman | 4cae19f94f | 4 months ago |
Herman Slatman | af76ebdc1d | 4 months ago |
github-actions[bot] | 2e9c5bbc1a | 4 months ago |
Herman Slatman | a7a4b47ead | 4 months ago |
Herman Slatman | 01de10d447 | 4 months ago |
Herman Slatman | fa5117adaa | 4 months ago |
Herman Slatman | bbb80cde16 | 4 months ago |
Herman Slatman | f02d4546a9 | 4 months ago |
dependabot[bot] | 3656b458ea | 4 months ago |
Carl Tashian | e542a26d4b | 5 months ago |
@ -1,615 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/acme/db/nosql"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/authority/provisioner/wire"
|
||||
nosqlDB "github.com/smallstep/nosql"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "test.ca.smallstep.com"
|
||||
linkerPrefix = "acme"
|
||||
)
|
||||
|
||||
func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) *provisioner.ACME {
|
||||
t.Helper()
|
||||
prov := &provisioner.ACME{
|
||||
Type: "ACME",
|
||||
Name: "test@acme-<test>provisioner.com",
|
||||
Options: options,
|
||||
Challenges: []provisioner.ACMEChallenge{
|
||||
provisioner.WIREOIDC_01,
|
||||
provisioner.WIREDPOP_01,
|
||||
},
|
||||
}
|
||||
|
||||
err := prov.Init(provisioner.Config{
|
||||
Claims: config.GlobalProvisionerClaims,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return prov
|
||||
}
|
||||
|
||||
// TODO(hs): replace with test CA server + acmez based test client for
|
||||
// more realistic integration test?
|
||||
func TestWireIntegration(t *testing.T) {
|
||||
accessTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
accessTokenSignerPEMBlock, err := pemutil.Serialize(accessTokenSignerJWK.Public().Key)
|
||||
require.NoError(t, err)
|
||||
accessTokenSignerPEMBytes := pem.EncodeToMemory(accessTokenSignerPEMBlock)
|
||||
|
||||
accessTokenSigner, err := jose.NewSigner(jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(accessTokenSignerJWK.Algorithm),
|
||||
Key: accessTokenSignerJWK,
|
||||
}, new(jose.SignerOptions))
|
||||
require.NoError(t, err)
|
||||
|
||||
oidcTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
require.NoError(t, err)
|
||||
oidcTokenSigner, err := jose.NewSigner(jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(oidcTokenSignerJWK.Algorithm),
|
||||
Key: oidcTokenSignerJWK,
|
||||
}, new(jose.SignerOptions))
|
||||
require.NoError(t, err)
|
||||
|
||||
prov := newWireProvisionerWithOptions(t, &provisioner.Options{
|
||||
X509: &provisioner.X509Options{
|
||||
Template: `{
|
||||
"subject": {
|
||||
"organization": "WireTest",
|
||||
"commonName": {{ toJson .Oidc.name }}
|
||||
},
|
||||
"uris": [{{ toJson .Oidc.preferred_username }}, {{ toJson .Dpop.sub }}],
|
||||
"keyUsage": ["digitalSignature"],
|
||||
"extKeyUsage": ["clientAuth"]
|
||||
}`,
|
||||
},
|
||||
Wire: &wire.Options{
|
||||
OIDC: &wire.OIDCOptions{
|
||||
Provider: &wire.Provider{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
AuthURL: "",
|
||||
TokenURL: "",
|
||||
JWKSURL: "",
|
||||
UserInfoURL: "",
|
||||
Algorithms: []string{"ES256"},
|
||||
},
|
||||
Config: &wire.Config{
|
||||
ClientID: "integration test",
|
||||
SignatureAlgorithms: []string{"ES256"},
|
||||
SkipClientIDCheck: true,
|
||||
SkipExpiryCheck: true,
|
||||
SkipIssuerCheck: true,
|
||||
InsecureSkipSignatureCheck: true, // NOTE: this skips actual token verification
|
||||
Now: time.Now,
|
||||
},
|
||||
TransformTemplate: "",
|
||||
},
|
||||
DPOP: &wire.DPOPOptions{
|
||||
SigningKey: accessTokenSignerPEMBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// mock provisioner and linker
|
||||
ctx := context.Background()
|
||||
ctx = acme.NewProvisionerContext(ctx, prov)
|
||||
ctx = acme.NewLinkerContext(ctx, acme.NewLinker(baseURL, linkerPrefix))
|
||||
|
||||
// create temporary BoltDB file
|
||||
file, err := os.CreateTemp(os.TempDir(), "integration-db-")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("database file name:", file.Name())
|
||||
dbFn := file.Name()
|
||||
err = file.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// open BoltDB
|
||||
rawDB, err := nosqlDB.New(nosqlDB.BBoltDriver, dbFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create tables
|
||||
db, err := nosql.New(rawDB)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make DB available to handlers
|
||||
ctx = acme.NewDatabaseContext(ctx, db)
|
||||
|
||||
// simulate signed payloads by making the signing key available in ctx
|
||||
jwk, err := jose.GenerateJWK("OKP", "", "EdDSA", "sig", "", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
ed25519PrivKey, ok := jwk.Key.(ed25519.PrivateKey)
|
||||
require.True(t, ok)
|
||||
|
||||
dpopSigner, err := jose.NewSigner(jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
|
||||
Key: jwk,
|
||||
}, new(jose.SignerOptions))
|
||||
require.NoError(t, err)
|
||||
|
||||
ed25519PubKey, ok := ed25519PrivKey.Public().(ed25519.PublicKey)
|
||||
require.True(t, ok)
|
||||
|
||||
jwk.Key = ed25519PubKey
|
||||
ctx = context.WithValue(ctx, jwkContextKey, jwk)
|
||||
|
||||
// get directory
|
||||
dir := func(ctx context.Context) (dir Directory) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/foo/bar", http.NoBody)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
GetDirectory(w, req)
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}(ctx)
|
||||
t.Log("directory:", dir)
|
||||
|
||||
// get nonce
|
||||
nonce := func(ctx context.Context) (nonce string) {
|
||||
req := httptest.NewRequest(http.MethodGet, dir.NewNonce, http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
addNonce(GetNonce)(w, req)
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
nonce = res.Header["Replay-Nonce"][0]
|
||||
return
|
||||
}(ctx)
|
||||
t.Log("nonce:", nonce)
|
||||
|
||||
// create new account
|
||||
acc := func(ctx context.Context) (acc *acme.Account) {
|
||||
// create payload
|
||||
nar := &NewAccountRequest{
|
||||
Contact: []string{"foo", "bar"},
|
||||
}
|
||||
rawNar, err := json.Marshal(nar)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create account
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: rawNar})
|
||||
req := httptest.NewRequest(http.MethodGet, dir.NewAccount, http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
NewAccount(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusCreated, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
locationParts := strings.Split(res.Header["Location"][0], "/")
|
||||
acc, err = db.GetAccount(ctx, locationParts[len(locationParts)-1])
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}(ctx)
|
||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||
t.Log("account ID:", acc.ID)
|
||||
|
||||
// new order
|
||||
order := func(ctx context.Context) (order *acme.Order) {
|
||||
mockMustAuthority(t, &mockCA{})
|
||||
nor := &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{
|
||||
Type: "wireapp-user",
|
||||
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
|
||||
},
|
||||
{
|
||||
Type: "wireapp-device",
|
||||
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(nor)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
|
||||
req := httptest.NewRequest("POST", "https://random.local/", http.NoBody)
|
||||
req = req.WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
NewOrder(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusCreated, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &order)
|
||||
require.NoError(t, err)
|
||||
|
||||
order, err = db.GetOrder(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}(ctx)
|
||||
t.Log("authzs IDs:", order.AuthorizationIDs)
|
||||
|
||||
// get authorization
|
||||
getAuthz := func(ctx context.Context, authzID string) (az *acme.Authorization) {
|
||||
chiCtx := chi.NewRouteContext()
|
||||
chiCtx.URLParams.Add("authzID", authzID)
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
GetAuthorization(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &az)
|
||||
require.NoError(t, err)
|
||||
|
||||
az, err = db.GetAuthorization(ctx, authzID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
var azs []*acme.Authorization
|
||||
for _, azID := range order.AuthorizationIDs {
|
||||
az := getAuthz(ctx, azID)
|
||||
azs = append(azs, az)
|
||||
for _, challenge := range az.Challenges {
|
||||
chiCtx := chi.NewRouteContext()
|
||||
chiCtx.URLParams.Add("chID", challenge.ID)
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||
|
||||
var payload []byte
|
||||
switch challenge.Type {
|
||||
case acme.WIREDPOP01:
|
||||
dpopBytes, err := json.Marshal(struct {
|
||||
jose.Claims
|
||||
Challenge string `json:"chal,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
HTU string `json:"htu,omitempty"`
|
||||
}{
|
||||
Claims: jose.Claims{
|
||||
Subject: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com",
|
||||
},
|
||||
Challenge: "token",
|
||||
Handle: "wireapp://%40alice.smith.qa@example.com",
|
||||
Nonce: "nonce",
|
||||
HTU: "http://issuer.example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dpop, err := dpopSigner.Sign(dpopBytes)
|
||||
require.NoError(t, err)
|
||||
proof, err := dpop.CompactSerialize()
|
||||
require.NoError(t, err)
|
||||
tokenBytes, err := json.Marshal(struct {
|
||||
jose.Claims
|
||||
Challenge string `json:"chal,omitempty"`
|
||||
Cnf struct {
|
||||
Kid string `json:"kid,omitempty"`
|
||||
} `json:"cnf"`
|
||||
Proof string `json:"proof,omitempty"`
|
||||
ClientID string `json:"client_id"`
|
||||
APIVersion int `json:"api_version"`
|
||||
Scope string `json:"scope"`
|
||||
}{
|
||||
Claims: jose.Claims{
|
||||
Issuer: "http://issuer.example.com",
|
||||
Audience: []string{"test"},
|
||||
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
|
||||
},
|
||||
Challenge: "token",
|
||||
Cnf: struct {
|
||||
Kid string `json:"kid,omitempty"`
|
||||
}{
|
||||
Kid: jwk.KeyID,
|
||||
},
|
||||
Proof: proof,
|
||||
ClientID: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com",
|
||||
APIVersion: 5,
|
||||
Scope: "wire_client_id",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
signed, err := accessTokenSigner.Sign(tokenBytes)
|
||||
require.NoError(t, err)
|
||||
accessToken, err := signed.CompactSerialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
p, err := json.Marshal(struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}{
|
||||
AccessToken: accessToken,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
payload = p
|
||||
case acme.WIREOIDC01:
|
||||
keyAuth, err := acme.KeyAuthorization("token", jwk)
|
||||
require.NoError(t, err)
|
||||
tokenBytes, err := json.Marshal(struct {
|
||||
jose.Claims
|
||||
Name string `json:"name,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
KeyAuth string `json:"keyauth"`
|
||||
}{
|
||||
Claims: jose.Claims{
|
||||
Issuer: "https://issuer.example.com",
|
||||
Audience: []string{"test"},
|
||||
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
|
||||
},
|
||||
Name: "Alice Smith",
|
||||
PreferredUsername: "wireapp://%40alice_wire@wire.com",
|
||||
KeyAuth: keyAuth,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
signed, err := oidcTokenSigner.Sign(tokenBytes)
|
||||
require.NoError(t, err)
|
||||
idToken, err := signed.CompactSerialize()
|
||||
require.NoError(t, err)
|
||||
p, err := json.Marshal(struct {
|
||||
IDToken string `json:"id_token"`
|
||||
}{
|
||||
IDToken: idToken,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
payload = p
|
||||
default:
|
||||
require.Fail(t, "unexpected challenge payload type")
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payload})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
GetChallenge(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close() //nolint:gocritic // close the body
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &challenge)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("challenge:", challenge.ID, challenge.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// get/validate challenge simulation
|
||||
updateAz := func(ctx context.Context, az *acme.Authorization) (updatedAz *acme.Authorization) {
|
||||
now := clock.Now().Format(time.RFC3339)
|
||||
for _, challenge := range az.Challenges {
|
||||
challenge.Status = acme.StatusValid
|
||||
challenge.ValidatedAt = now
|
||||
err := db.UpdateChallenge(ctx, challenge)
|
||||
if err != nil {
|
||||
t.Error("updating challenge", challenge.ID, ":", err)
|
||||
}
|
||||
}
|
||||
|
||||
updatedAz, err = db.GetAuthorization(ctx, az.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
for _, az := range azs {
|
||||
updatedAz := updateAz(ctx, az)
|
||||
for _, challenge := range updatedAz.Challenges {
|
||||
t.Log("updated challenge:", challenge.ID, challenge.Status)
|
||||
switch challenge.Type {
|
||||
case acme.WIREOIDC01:
|
||||
err = db.CreateOidcToken(ctx, order.ID, map[string]any{"name": "Smith, Alice M (QA)", "preferred_username": "wireapp://%40alice.smith.qa@example.com"})
|
||||
require.NoError(t, err)
|
||||
case acme.WIREDPOP01:
|
||||
err = db.CreateDpopToken(ctx, order.ID, map[string]any{"sub": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com"})
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
require.Fail(t, "unexpected challenge type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get order
|
||||
updatedOrder := func(ctx context.Context) (updatedOrder *acme.Order) {
|
||||
chiCtx := chi.NewRouteContext()
|
||||
chiCtx.URLParams.Add("ordID", order.ID)
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
GetOrder(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &updatedOrder)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, acme.StatusReady, updatedOrder.Status)
|
||||
|
||||
return
|
||||
}(ctx)
|
||||
t.Log("updated order status:", updatedOrder.Status)
|
||||
|
||||
// finalize order
|
||||
finalizedOrder := func(ctx context.Context) (finalizedOrder *acme.Order) {
|
||||
ca, err := minica.New(minica.WithName("WireTestCA"))
|
||||
require.NoError(t, err)
|
||||
mockMustAuthority(t, &mockCASigner{
|
||||
signer: func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
var (
|
||||
certOptions []x509util.Option
|
||||
)
|
||||
for _, op := range extraOpts {
|
||||
if k, ok := op.(provisioner.CertificateOptions); ok {
|
||||
certOptions = append(certOptions, k.Options(signOpts)...)
|
||||
}
|
||||
}
|
||||
|
||||
x509utilTemplate, err := x509util.NewCertificate(csr, certOptions...)
|
||||
require.NoError(t, err)
|
||||
|
||||
template := x509utilTemplate.GetCertificate()
|
||||
require.NotNil(t, template)
|
||||
|
||||
cert, err := ca.Sign(template)
|
||||
require.NoError(t, err)
|
||||
|
||||
u1, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
|
||||
require.NoError(t, err)
|
||||
u2, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA%21ed416ce8ecdd9fad@example.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*url.URL{u1, u2}, cert.URIs)
|
||||
assert.Equal(t, "Smith, Alice M (QA)", cert.Subject.CommonName)
|
||||
|
||||
return []*x509.Certificate{cert, ca.Intermediate}, nil
|
||||
},
|
||||
})
|
||||
|
||||
qUserID, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
qUserName, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
csrTemplate := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"example.com"},
|
||||
ExtraNames: []pkix.AttributeTypeAndValue{
|
||||
{
|
||||
Type: asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 3, 1, 241},
|
||||
Value: "Smith, Alice M (QA)",
|
||||
},
|
||||
},
|
||||
},
|
||||
URIs: []*url.URL{
|
||||
qUserName,
|
||||
qUserID,
|
||||
},
|
||||
SignatureAlgorithm: x509.PureEd25519,
|
||||
}
|
||||
|
||||
csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, priv)
|
||||
require.NoError(t, err)
|
||||
|
||||
fr := FinalizeRequest{CSR: base64.RawURLEncoding.EncodeToString(csr)}
|
||||
frRaw, err := json.Marshal(fr)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: frRaw})
|
||||
|
||||
chiCtx := chi.NewRouteContext()
|
||||
chiCtx.URLParams.Add("ordID", order.ID)
|
||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
|
||||
w := httptest.NewRecorder()
|
||||
FinalizeOrder(w, req)
|
||||
|
||||
res := w.Result()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(bytes.TrimSpace(body), &finalizedOrder)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, acme.StatusValid, finalizedOrder.Status)
|
||||
|
||||
finalizedOrder, err = db.GetOrder(ctx, order.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}(ctx)
|
||||
t.Log("finalized order status:", finalizedOrder.Status)
|
||||
}
|
||||
|
||||
type mockCASigner struct {
|
||||
signer func(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
}
|
||||
|
||||
func (m *mockCASigner) SignWithContext(_ context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
if m.signer == nil {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
return m.signer(cr, opts, signOpts...)
|
||||
}
|
||||
|
||||
func (m *mockCASigner) AreSANsAllowed(ctx context.Context, sans []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCASigner) IsRevoked(sn string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *mockCASigner) Revoke(ctx context.Context, opts *authority.RevokeOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCASigner) LoadProvisionerByName(string) (provisioner.Interface, error) {
|
||||
return nil, nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,121 +0,0 @@
|
||||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/nosql"
|
||||
)
|
||||
|
||||
type dbDpopToken struct {
|
||||
ID string `json:"id"`
|
||||
Content []byte `json:"content"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// getDBDpopToken retrieves and unmarshals an DPoP type from the database.
|
||||
func (db *DB) getDBDpopToken(_ context.Context, orderID string) (*dbDpopToken, error) {
|
||||
b, err := db.db.Get(wireDpopTokenTable, []byte(orderID))
|
||||
if err != nil {
|
||||
if nosql.IsErrNotFound(err) {
|
||||
return nil, acme.NewError(acme.ErrorMalformedType, "dpop token %q not found", orderID)
|
||||
}
|
||||
return nil, fmt.Errorf("failed loading dpop token %q: %w", orderID, err)
|
||||
}
|
||||
|
||||
d := new(dbDpopToken)
|
||||
if err := json.Unmarshal(b, d); err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshaling dpop token %q into dbDpopToken: %w", orderID, err)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// GetDpopToken retrieves an DPoP from the database.
|
||||
func (db *DB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) {
|
||||
dbDpop, err := db.getDBDpopToken(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dpop := make(map[string]any)
|
||||
err = json.Unmarshal(dbDpop.Content, &dpop)
|
||||
|
||||
return dpop, err
|
||||
}
|
||||
|
||||
// CreateDpopToken creates DPoP resources and saves them to the DB.
|
||||
func (db *DB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error {
|
||||
content, err := json.Marshal(dpop)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshaling dpop token: %w", err)
|
||||
}
|
||||
|
||||
now := clock.Now()
|
||||
dbDpop := &dbDpopToken{
|
||||
ID: orderID,
|
||||
Content: content,
|
||||
CreatedAt: now,
|
||||
}
|
||||
if err := db.save(ctx, orderID, dbDpop, nil, "dpop", wireDpopTokenTable); err != nil {
|
||||
return fmt.Errorf("failed saving dpop token: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dbOidcToken struct {
|
||||
ID string `json:"id"`
|
||||
Content []byte `json:"content"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// getDBOidcToken retrieves and unmarshals an OIDC id token type from the database.
|
||||
func (db *DB) getDBOidcToken(_ context.Context, orderID string) (*dbOidcToken, error) {
|
||||
b, err := db.db.Get(wireOidcTokenTable, []byte(orderID))
|
||||
if err != nil {
|
||||
if nosql.IsErrNotFound(err) {
|
||||
return nil, acme.NewError(acme.ErrorMalformedType, "oidc token %q not found", orderID)
|
||||
}
|
||||
return nil, fmt.Errorf("failed loading oidc token %q: %w", orderID, err)
|
||||
}
|
||||
|
||||
o := new(dbOidcToken)
|
||||
if err := json.Unmarshal(b, o); err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshaling oidc token %q into dbOidcToken: %w", orderID, err)
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// GetOidcToken retrieves an oidc token from the database.
|
||||
func (db *DB) GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) {
|
||||
dbOidc, err := db.getDBOidcToken(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idToken := make(map[string]any)
|
||||
err = json.Unmarshal(dbOidc.Content, &idToken)
|
||||
|
||||
return idToken, err
|
||||
}
|
||||
|
||||
// CreateOidcToken creates oidc token resources and saves them to the DB.
|
||||
func (db *DB) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error {
|
||||
content, err := json.Marshal(idToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed marshaling oidc token: %w", err)
|
||||
}
|
||||
|
||||
now := clock.Now()
|
||||
dbOidc := &dbOidcToken{
|
||||
ID: orderID,
|
||||
Content: content,
|
||||
CreatedAt: now,
|
||||
}
|
||||
if err := db.save(ctx, orderID, dbOidc, nil, "oidc", wireOidcTokenTable); err != nil {
|
||||
return fmt.Errorf("failed saving oidc token: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,394 +0,0 @@
|
||||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/acme"
|
||||
certificatesdb "github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/nosql"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDB_GetDpopToken(t *testing.T) {
|
||||
type test struct {
|
||||
db *DB
|
||||
orderID string
|
||||
expected map[string]any
|
||||
expectedErr error
|
||||
}
|
||||
var tests = map[string]func(t *testing.T) test{
|
||||
"fail/acme-not-found": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: &acme.Error{
|
||||
Type: "urn:ietf:params:acme:error:malformed",
|
||||
Status: 400,
|
||||
Detail: "The request message was malformed",
|
||||
Err: errors.New(`dpop token "orderID" not found`),
|
||||
},
|
||||
}
|
||||
},
|
||||
"fail/unmarshal-error": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
token := dbDpopToken{
|
||||
ID: "orderID",
|
||||
Content: []byte("{}"),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
b, err := json.Marshal(token)
|
||||
require.NoError(t, err)
|
||||
err = db.Set(wireDpopTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: errors.New(`failed unmarshaling dpop token "orderID" into dbDpopToken: invalid character ':' after top-level value`),
|
||||
}
|
||||
},
|
||||
"fail/db.Get": func(t *testing.T) test {
|
||||
db := &certificatesdb.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equal(t, wireDpopTokenTable, bucket)
|
||||
assert.Equal(t, []byte("orderID"), key)
|
||||
return nil, errors.New("fail")
|
||||
},
|
||||
}
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: errors.New(`failed loading dpop token "orderID": fail`),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
token := dbDpopToken{
|
||||
ID: "orderID",
|
||||
Content: []byte(`{"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com"}`),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
b, err := json.Marshal(token)
|
||||
require.NoError(t, err)
|
||||
err = db.Set(wireDpopTokenTable, []byte("orderID"), b)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expected: map[string]any{
|
||||
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
tc := run(t)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := tc.db.GetDpopToken(context.Background(), tc.orderID)
|
||||
if tc.expectedErr != nil {
|
||||
assert.EqualError(t, err, tc.expectedErr.Error())
|
||||
ae := &acme.Error{}
|
||||
if errors.As(err, &ae) {
|
||||
ee := &acme.Error{}
|
||||
require.True(t, errors.As(tc.expectedErr, &ee))
|
||||
assert.Equal(t, ee.Detail, ae.Detail)
|
||||
assert.Equal(t, ee.Type, ae.Type)
|
||||
assert.Equal(t, ee.Status, ae.Status)
|
||||
}
|
||||
assert.Nil(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_CreateDpopToken(t *testing.T) {
|
||||
type test struct {
|
||||
db *DB
|
||||
orderID string
|
||||
dpop map[string]any
|
||||
expectedErr error
|
||||
}
|
||||
var tests = map[string]func(t *testing.T) test{
|
||||
"fail/db.Save": func(t *testing.T) test {
|
||||
db := &certificatesdb.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
assert.Equal(t, wireDpopTokenTable, bucket)
|
||||
assert.Equal(t, []byte("orderID"), key)
|
||||
return nil, false, errors.New("fail")
|
||||
},
|
||||
}
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
dpop: map[string]any{
|
||||
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
|
||||
},
|
||||
expectedErr: errors.New("failed saving dpop token: error saving acme dpop: fail"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
dpop: map[string]any{
|
||||
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/nil": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
dpop: nil,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
tc := run(t)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.db.CreateDpopToken(context.Background(), tc.orderID, tc.dpop)
|
||||
if tc.expectedErr != nil {
|
||||
assert.EqualError(t, err, tc.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
dpop, err := tc.db.getDBDpopToken(context.Background(), tc.orderID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.orderID, dpop.ID)
|
||||
var m map[string]any
|
||||
err = json.Unmarshal(dpop.Content, &m)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.dpop, m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_GetOidcToken(t *testing.T) {
|
||||
type test struct {
|
||||
db *DB
|
||||
orderID string
|
||||
expected map[string]any
|
||||
expectedErr error
|
||||
}
|
||||
var tests = map[string]func(t *testing.T) test{
|
||||
"fail/acme-not-found": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: &acme.Error{
|
||||
Type: "urn:ietf:params:acme:error:malformed",
|
||||
Status: 400,
|
||||
Detail: "The request message was malformed",
|
||||
Err: errors.New(`oidc token "orderID" not found`),
|
||||
},
|
||||
}
|
||||
},
|
||||
"fail/unmarshal-error": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
token := dbOidcToken{
|
||||
ID: "orderID",
|
||||
Content: []byte("{}"),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
b, err := json.Marshal(token)
|
||||
require.NoError(t, err)
|
||||
err = db.Set(wireOidcTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: errors.New(`failed unmarshaling oidc token "orderID" into dbOidcToken: invalid character ':' after top-level value`),
|
||||
}
|
||||
},
|
||||
"fail/db.Get": func(t *testing.T) test {
|
||||
db := &certificatesdb.MockNoSQLDB{
|
||||
MGet: func(bucket, key []byte) ([]byte, error) {
|
||||
assert.Equal(t, wireOidcTokenTable, bucket)
|
||||
assert.Equal(t, []byte("orderID"), key)
|
||||
return nil, errors.New("fail")
|
||||
},
|
||||
}
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expectedErr: errors.New(`failed loading oidc token "orderID": fail`),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
token := dbOidcToken{
|
||||
ID: "orderID",
|
||||
Content: []byte(`{"name": "Alice Smith", "preferred_username": "@alice.smith"}`),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
b, err := json.Marshal(token)
|
||||
require.NoError(t, err)
|
||||
err = db.Set(wireOidcTokenTable, []byte("orderID"), b)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
expected: map[string]any{
|
||||
"name": "Alice Smith",
|
||||
"preferred_username": "@alice.smith",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
tc := run(t)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := tc.db.GetOidcToken(context.Background(), tc.orderID)
|
||||
if tc.expectedErr != nil {
|
||||
assert.EqualError(t, err, tc.expectedErr.Error())
|
||||
ae := &acme.Error{}
|
||||
if errors.As(err, &ae) {
|
||||
ee := &acme.Error{}
|
||||
require.True(t, errors.As(tc.expectedErr, &ee))
|
||||
assert.Equal(t, ee.Detail, ae.Detail)
|
||||
assert.Equal(t, ee.Type, ae.Type)
|
||||
assert.Equal(t, ee.Status, ae.Status)
|
||||
}
|
||||
assert.Nil(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_CreateOidcToken(t *testing.T) {
|
||||
type test struct {
|
||||
db *DB
|
||||
orderID string
|
||||
oidc map[string]any
|
||||
expectedErr error
|
||||
}
|
||||
var tests = map[string]func(t *testing.T) test{
|
||||
"fail/db.Save": func(t *testing.T) test {
|
||||
db := &certificatesdb.MockNoSQLDB{
|
||||
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
|
||||
assert.Equal(t, wireOidcTokenTable, bucket)
|
||||
assert.Equal(t, []byte("orderID"), key)
|
||||
return nil, false, errors.New("fail")
|
||||
},
|
||||
}
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
oidc: map[string]any{
|
||||
"name": "Alice Smith",
|
||||
"preferred_username": "@alice.smith",
|
||||
},
|
||||
expectedErr: errors.New("failed saving oidc token: error saving acme oidc: fail"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
oidc: map[string]any{
|
||||
"name": "Alice Smith",
|
||||
"preferred_username": "@alice.smith",
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/nil": func(t *testing.T) test {
|
||||
dir := t.TempDir()
|
||||
db, err := nosql.New("badgerv2", dir)
|
||||
require.NoError(t, err)
|
||||
return test{
|
||||
db: &DB{
|
||||
db: db,
|
||||
},
|
||||
orderID: "orderID",
|
||||
oidc: nil,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
tc := run(t)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.db.CreateOidcToken(context.Background(), tc.orderID, tc.oidc)
|
||||
if tc.expectedErr != nil {
|
||||
assert.EqualError(t, err, tc.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
oidc, err := tc.db.getDBOidcToken(context.Background(), tc.orderID)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.orderID, oidc.ID)
|
||||
var m map[string]any
|
||||
err = json.Unmarshal(oidc.Content, &m)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.oidc, m)
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsErrNotFound(t *testing.T) {
|
||||
type args struct {
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"true ErrNotFound", args{ErrNotFound}, true},
|
||||
{"true sql.ErrNoRows", args{sql.ErrNoRows}, true},
|
||||
{"true wrapped ErrNotFound", args{fmt.Errorf("something failed: %w", ErrNotFound)}, true},
|
||||
{"true wrapped sql.ErrNoRows", args{fmt.Errorf("something failed: %w", sql.ErrNoRows)}, true},
|
||||
{"false other", args{errors.New("not found")}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsErrNotFound(tt.args.err); got != tt.want {
|
||||
t.Errorf("IsErrNotFound() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.step.sm/crypto/kms/uri"
|
||||
)
|
||||
|
||||
type UserID struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
type DeviceID struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
ClientID string `json:"client-id,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
func ParseUserID(value string) (id UserID, err error) {
|
||||
if err = json.Unmarshal([]byte(value), &id); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case id.Handle == "":
|
||||
err = errors.New("handle must not be empty")
|
||||
case id.Name == "":
|
||||
err = errors.New("name must not be empty")
|
||||
case id.Domain == "":
|
||||
err = errors.New("domain must not be empty")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseDeviceID(value string) (id DeviceID, err error) {
|
||||
if err = json.Unmarshal([]byte(value), &id); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case id.Handle == "":
|
||||
err = errors.New("handle must not be empty")
|
||||
case id.Name == "":
|
||||
err = errors.New("name must not be empty")
|
||||
case id.Domain == "":
|
||||
err = errors.New("domain must not be empty")
|
||||
case id.ClientID == "":
|
||||
err = errors.New("client-id must not be empty")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ClientID struct {
|
||||
Scheme string
|
||||
Username string
|
||||
DeviceID string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// ParseClientID parses a Wire clientID. The ClientID format is as follows:
|
||||
//
|
||||
// "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
|
||||
//
|
||||
// where '!' is used as a separator between the user id & device id.
|
||||
func ParseClientID(clientID string) (ClientID, error) {
|
||||
clientIDURI, err := uri.Parse(clientID)
|
||||
if err != nil {
|
||||
return ClientID{}, fmt.Errorf("invalid Wire client ID URI %q: %w", clientID, err)
|
||||
}
|
||||
if clientIDURI.Scheme != "wireapp" {
|
||||
return ClientID{}, fmt.Errorf("invalid Wire client ID scheme %q; expected \"wireapp\"", clientIDURI.Scheme)
|
||||
}
|
||||
fullUsername := clientIDURI.User.Username()
|
||||
parts := strings.SplitN(fullUsername, "!", 2)
|
||||
if len(parts) != 2 {
|
||||
return ClientID{}, fmt.Errorf("invalid Wire client ID username %q", fullUsername)
|
||||
}
|
||||
return ClientID{
|
||||
Scheme: clientIDURI.Scheme,
|
||||
Username: parts[0],
|
||||
DeviceID: parts[1],
|
||||
Domain: clientIDURI.Host,
|
||||
}, nil
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseUserID(t *testing.T) {
|
||||
ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
failJSON := `{"name": }`
|
||||
emptyHandle := `{"name": "Alice Smith", "domain": "wire.com", "handle": ""}`
|
||||
emptyName := `{"name": "", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
emptyDomain := `{"name": "Alice Smith", "domain": "", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
wantWireID UserID
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "ok", value: ok, wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
|
||||
{name: "fail/json", value: failJSON, wantErr: true},
|
||||
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
|
||||
{name: "fail/empty-name", value: emptyName, wantErr: true},
|
||||
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotWireID, err := ParseUserID(tt.value)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantWireID, gotWireID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDeviceID(t *testing.T) {
|
||||
ok := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
failJSON := `{"name": }`
|
||||
emptyHandle := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": ""}`
|
||||
emptyName := `{"name": "", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
emptyDomain := `{"name": "device", "domain": "", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
emptyClientID := `{"name": "device", "domain": "wire.com", "client-id": "", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
wantWireID DeviceID
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "ok", value: ok, wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
|
||||
{name: "fail/json", value: failJSON, wantErr: true},
|
||||
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
|
||||
{name: "fail/empty-name", value: emptyName, wantErr: true},
|
||||
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
|
||||
{name: "fail/empty-client-id", value: emptyClientID, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotWireID, err := ParseDeviceID(tt.value)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantWireID, gotWireID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseClientID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clientID string
|
||||
want ClientID
|
||||
expectedErr error
|
||||
}{
|
||||
{name: "ok", clientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", want: ClientID{Scheme: "wireapp", Username: "CzbfFjDOQrenCbDxVmgnFw", DeviceID: "594930e9d50bb175", Domain: "wire.com"}},
|
||||
{name: "fail/uri", clientID: "bla", expectedErr: errors.New(`invalid Wire client ID URI "bla": error parsing bla: scheme is missing`)},
|
||||
{name: "fail/scheme", clientID: "not-wireapp://bla.com", expectedErr: errors.New(`invalid Wire client ID scheme "not-wireapp"; expected "wireapp"`)},
|
||||
{name: "fail/username", clientID: "wireapp://user@wire.com", expectedErr: errors.New(`invalid Wire client ID username "user"`)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseClientID(tt.clientID)
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
type DPOPOptions struct {
|
||||
// Public part of the signing key for DPoP access token in PEM format
|
||||
SigningKey []byte `json:"key"`
|
||||
// URI template for the URI the ACME client must call to fetch the DPoP challenge proof (an access token from wire-server)
|
||||
Target string `json:"target"`
|
||||
|
||||
signingKey crypto.PublicKey
|
||||
target *template.Template
|
||||
}
|
||||
|
||||
func (o *DPOPOptions) GetSigningKey() crypto.PublicKey {
|
||||
return o.signingKey
|
||||
}
|
||||
|
||||
func (o *DPOPOptions) EvaluateTarget(deviceID string) (string, error) {
|
||||
if deviceID == "" {
|
||||
return "", errors.New("deviceID must not be empty")
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
|
||||
return "", fmt.Errorf("failed executing DPoP template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (o *DPOPOptions) validateAndInitialize() (err error) {
|
||||
o.signingKey, err = pemutil.Parse(o.SigningKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing key: %w", err)
|
||||
}
|
||||
o.target, err = template.New("DeviceID").Parse(o.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing DPoP template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDPOPOptions_EvaluateTarget(t *testing.T) {
|
||||
tu := "http://wire.com:15958/clients/{{.DeviceID}}/access-token"
|
||||
target, err := template.New("DeviceID").Parse(tu)
|
||||
require.NoError(t, err)
|
||||
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
|
||||
failTarget, err := template.New("DeviceID").Parse(fail)
|
||||
require.NoError(t, err)
|
||||
type fields struct {
|
||||
target *template.Template
|
||||
}
|
||||
type args struct {
|
||||
deviceID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://wire.com:15958/clients/deviceID/access-token",
|
||||
},
|
||||
{
|
||||
name: "fail/empty", fields: fields{target: target}, args: args{deviceID: ""}, expectedErr: errors.New("deviceID must not be empty"),
|
||||
},
|
||||
{
|
||||
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing DPoP template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &DPOPOptions{
|
||||
target: tt.fields.target,
|
||||
}
|
||||
got, err := o.EvaluateTarget(tt.args.deviceID)
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
assert.Empty(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
DiscoveryBaseURL string `json:"discoveryBaseUrl,omitempty"`
|
||||
IssuerURL string `json:"issuerUrl,omitempty"`
|
||||
AuthURL string `json:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty"`
|
||||
JWKSURL string `json:"jwksUrl,omitempty"`
|
||||
UserInfoURL string `json:"userInfoUrl,omitempty"`
|
||||
Algorithms []string `json:"signatureAlgorithms,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
SignatureAlgorithms []string `json:"signatureAlgorithms,omitempty"`
|
||||
|
||||
// the properties below are only used for testing
|
||||
SkipClientIDCheck bool `json:"-"`
|
||||
SkipExpiryCheck bool `json:"-"`
|
||||
SkipIssuerCheck bool `json:"-"`
|
||||
InsecureSkipSignatureCheck bool `json:"-"`
|
||||
Now func() time.Time `json:"-"`
|
||||
}
|
||||
|
||||
type OIDCOptions struct {
|
||||
Provider *Provider `json:"provider,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
TransformTemplate string `json:"transform,omitempty"`
|
||||
|
||||
target *template.Template
|
||||
transform *template.Template
|
||||
oidcProviderConfig *oidc.ProviderConfig
|
||||
provider *oidc.Provider
|
||||
verifier *oidc.IDTokenVerifier
|
||||
}
|
||||
|
||||
func (o *OIDCOptions) GetVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
|
||||
if o.verifier == nil {
|
||||
switch {
|
||||
case o.Provider.DiscoveryBaseURL != "":
|
||||
// creates a new OIDC provider using automatic discovery and the default HTTP client
|
||||
provider, err := oidc.NewProvider(ctx, o.Provider.DiscoveryBaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating new OIDC provider using discovery: %w", err)
|
||||
}
|
||||
o.provider = provider
|
||||
default:
|
||||
o.provider = o.oidcProviderConfig.NewProvider(ctx)
|
||||
}
|
||||
|
||||
if o.provider == nil {
|
||||
return nil, errors.New("no OIDC provider available")
|
||||
}
|
||||
|
||||
o.verifier = o.provider.Verifier(o.getConfig())
|
||||
}
|
||||
|
||||
return o.verifier, nil
|
||||
}
|
||||
|
||||
func (o *OIDCOptions) getConfig() *oidc.Config {
|
||||
if o == nil || o.Config == nil {
|
||||
return &oidc.Config{}
|
||||
}
|
||||
|
||||
return &oidc.Config{
|
||||
ClientID: o.Config.ClientID,
|
||||
SupportedSigningAlgs: o.Config.SignatureAlgorithms,
|
||||
SkipClientIDCheck: o.Config.SkipClientIDCheck,
|
||||
SkipExpiryCheck: o.Config.SkipExpiryCheck,
|
||||
SkipIssuerCheck: o.Config.SkipIssuerCheck,
|
||||
Now: o.Config.Now,
|
||||
InsecureSkipSignatureCheck: o.Config.InsecureSkipSignatureCheck,
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTemplate = `{"name": "{{ .name }}", "preferred_username": "{{ .preferred_username }}"}`
|
||||
|
||||
func (o *OIDCOptions) validateAndInitialize() (err error) {
|
||||
if o.Provider == nil {
|
||||
return errors.New("provider not set")
|
||||
}
|
||||
if o.Provider.IssuerURL == "" && o.Provider.DiscoveryBaseURL == "" {
|
||||
return errors.New("either OIDC discovery or issuer URL must be set")
|
||||
}
|
||||
|
||||
if o.Provider.DiscoveryBaseURL == "" {
|
||||
o.oidcProviderConfig, err = toOIDCProviderConfig(o.Provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed creationg OIDC provider config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
o.target, err = template.New("DeviceID").Parse(o.Provider.IssuerURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing OIDC template: %w", err)
|
||||
}
|
||||
|
||||
o.transform, err = parseTransform(o.TransformTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing OIDC transformation template: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTransform(transformTemplate string) (*template.Template, error) {
|
||||
if transformTemplate == "" {
|
||||
transformTemplate = defaultTemplate
|
||||
}
|
||||
|
||||
return template.New("transform").Funcs(x509util.GetFuncMap()).Parse(transformTemplate)
|
||||
}
|
||||
|
||||
func (o *OIDCOptions) EvaluateTarget(deviceID string) (string, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
|
||||
return "", fmt.Errorf("failed executing OIDC template: %w", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (o *OIDCOptions) Transform(v map[string]any) (map[string]any, error) {
|
||||
if o.transform == nil || v == nil {
|
||||
return v, nil
|
||||
}
|
||||
// TODO(hs): add support for extracting error message from template "fail" function?
|
||||
buf := new(bytes.Buffer)
|
||||
if err := o.transform.Execute(buf, v); err != nil {
|
||||
return nil, fmt.Errorf("failed executing OIDC transformation: %w", err)
|
||||
}
|
||||
var r map[string]any
|
||||
if err := json.Unmarshal(buf.Bytes(), &r); err != nil {
|
||||
return nil, fmt.Errorf("failed unmarshaling transformed OIDC token: %w", err)
|
||||
}
|
||||
// add original claims if not yet in the transformed result
|
||||
for key, value := range v {
|
||||
if _, ok := r[key]; !ok {
|
||||
r[key] = value
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func toOIDCProviderConfig(in *Provider) (*oidc.ProviderConfig, error) {
|
||||
issuerURL, err := url.Parse(in.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing issuer URL: %w", err)
|
||||
}
|
||||
// Removes query params from the URL because we use it as a way to notify client about the actual OAuth ClientId
|
||||
// for this provisioner.
|
||||
// This URL is going to look like: "https://idp:5556/dex?clientid=foo"
|
||||
// If we don't trim the query params here i.e. 'clientid' then the idToken verification is going to fail because
|
||||
// the 'iss' claim of the idToken will be "https://idp:5556/dex"
|
||||
issuerURL.RawQuery = ""
|
||||
issuerURL.Fragment = ""
|
||||
return &oidc.ProviderConfig{
|
||||
IssuerURL: issuerURL.String(),
|
||||
AuthURL: in.AuthURL,
|
||||
TokenURL: in.TokenURL,
|
||||
UserInfoURL: in.UserInfoURL,
|
||||
JWKSURL: in.JWKSURL,
|
||||
Algorithms: in.Algorithms,
|
||||
}, nil
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestOIDCOptions_Transform(t *testing.T) {
|
||||
defaultTransform, err := parseTransform(``)
|
||||
require.NoError(t, err)
|
||||
swapTransform, err := parseTransform(`{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}`)
|
||||
require.NoError(t, err)
|
||||
funcTransform, err := parseTransform(`{"name": "{{ .name }}", "preferred_username": "{{ first .usernames }}"}`)
|
||||
require.NoError(t, err)
|
||||
type fields struct {
|
||||
transform *template.Template
|
||||
}
|
||||
type args struct {
|
||||
v map[string]any
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want map[string]any
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ok/no-transform",
|
||||
fields: fields{
|
||||
transform: nil,
|
||||
},
|
||||
args: args{
|
||||
v: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "Preferred",
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "Preferred",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/empty-data",
|
||||
fields: fields{
|
||||
transform: nil,
|
||||
},
|
||||
args: args{
|
||||
v: map[string]any{},
|
||||
},
|
||||
want: map[string]any{},
|
||||
},
|
||||
{
|
||||
name: "ok/default-transform",
|
||||
fields: fields{
|
||||
transform: defaultTransform,
|
||||
},
|
||||
args: args{
|
||||
v: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "Preferred",
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "Preferred",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/swap-transform",
|
||||
fields: fields{
|
||||
transform: swapTransform,
|
||||
},
|
||||
args: args{
|
||||
v: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "Preferred",
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"name": "Preferred",
|
||||
"preferred_username": "Example",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/transform-with-functions",
|
||||
fields: fields{
|
||||
transform: funcTransform,
|
||||
},
|
||||
args: args{
|
||||
v: map[string]any{
|
||||
"name": "Example",
|
||||
"usernames": []string{"name-1", "name-2", "name-3"},
|
||||
},
|
||||
},
|
||||
want: map[string]any{
|
||||
"name": "Example",
|
||||
"preferred_username": "name-1",
|
||||
"usernames": []string{"name-1", "name-2", "name-3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &OIDCOptions{
|
||||
transform: tt.fields.transform,
|
||||
}
|
||||
got, err := o.Transform(tt.args.v)
|
||||
if tt.expectedErr != nil {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCOptions_EvaluateTarget(t *testing.T) {
|
||||
tu := "http://target.example.com/{{.DeviceID}}"
|
||||
target, err := template.New("DeviceID").Parse(tu)
|
||||
require.NoError(t, err)
|
||||
empty := "http://target.example.com"
|
||||
emptyTarget, err := template.New("DeviceID").Parse(empty)
|
||||
require.NoError(t, err)
|
||||
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
|
||||
failTarget, err := template.New("DeviceID").Parse(fail)
|
||||
require.NoError(t, err)
|
||||
type fields struct {
|
||||
target *template.Template
|
||||
}
|
||||
type args struct {
|
||||
deviceID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://target.example.com/deviceID",
|
||||
},
|
||||
{
|
||||
name: "ok/empty", fields: fields{target: emptyTarget}, args: args{deviceID: ""}, want: "http://target.example.com",
|
||||
},
|
||||
{
|
||||
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing OIDC template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &OIDCOptions{
|
||||
target: tt.fields.target,
|
||||
}
|
||||
got, err := o.EvaluateTarget(tt.args.deviceID)
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
assert.Empty(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCOptions_GetVerifier(t *testing.T) {
|
||||
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
srv := mustDiscoveryServer(t, signerJWK.Public())
|
||||
defer srv.Close()
|
||||
type fields struct {
|
||||
Provider *Provider
|
||||
Config *Config
|
||||
TransformTemplate string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
ctx context.Context
|
||||
want *oidc.IDTokenVerifier
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "fail/invalid-discovery-url",
|
||||
fields: fields{
|
||||
Provider: &Provider{
|
||||
DiscoveryBaseURL: "http://invalid.example.com",
|
||||
},
|
||||
Config: &Config{
|
||||
ClientID: "client-id",
|
||||
},
|
||||
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
|
||||
},
|
||||
ctx: context.Background(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ok/auto",
|
||||
fields: fields{
|
||||
Provider: &Provider{
|
||||
DiscoveryBaseURL: srv.URL,
|
||||
},
|
||||
Config: &Config{
|
||||
ClientID: "client-id",
|
||||
},
|
||||
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
|
||||
},
|
||||
ctx: context.Background(),
|
||||
},
|
||||
{
|
||||
name: "ok/fixed",
|
||||
fields: fields{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "http://issuer.example.com",
|
||||
},
|
||||
Config: &Config{
|
||||
ClientID: "client-id",
|
||||
},
|
||||
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
|
||||
},
|
||||
ctx: context.Background(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &OIDCOptions{
|
||||
Provider: tt.fields.Provider,
|
||||
Config: tt.fields.Config,
|
||||
TransformTemplate: tt.fields.TransformTemplate,
|
||||
}
|
||||
|
||||
err := o.validateAndInitialize()
|
||||
require.NoError(t, err)
|
||||
|
||||
verifier, err := o.GetVerifier(tt.ctx)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, verifier)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, verifier)
|
||||
if assert.NotNil(t, o.provider) {
|
||||
assert.NotNil(t, o.provider.Endpoint())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustDiscoveryServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewServer(mux)
|
||||
b, err := json.Marshal(struct {
|
||||
Keys []jose.JSONWebKey `json:"keys,omitempty"`
|
||||
}{
|
||||
Keys: []jose.JSONWebKey{pub},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
jwks := string(b)
|
||||
|
||||
wellKnown := fmt.Sprintf(`{
|
||||
"issuer": "%[1]s",
|
||||
"authorization_endpoint": "%[1]s/auth",
|
||||
"token_endpoint": "%[1]s/token",
|
||||
"jwks_uri": "%[1]s/keys",
|
||||
"userinfo_endpoint": "%[1]s/userinfo",
|
||||
"id_token_signing_alg_values_supported": ["ES256"]
|
||||
}`, server.URL)
|
||||
|
||||
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) {
|
||||
_, err := io.WriteString(w, wellKnown)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) {
|
||||
_, err := io.WriteString(w, jwks)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
})
|
||||
|
||||
t.Cleanup(server.Close)
|
||||
return server
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Options holds the Wire ACME extension options
|
||||
type Options struct {
|
||||
OIDC *OIDCOptions `json:"oidc,omitempty"`
|
||||
DPOP *DPOPOptions `json:"dpop,omitempty"`
|
||||
}
|
||||
|
||||
// GetOIDCOptions returns the OIDC options.
|
||||
func (o *Options) GetOIDCOptions() *OIDCOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.OIDC
|
||||
}
|
||||
|
||||
// GetDPOPOptions returns the DPoP options.
|
||||
func (o *Options) GetDPOPOptions() *DPOPOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.DPOP
|
||||
}
|
||||
|
||||
// Validate validates and initializes the Wire OIDC and DPoP options.
|
||||
//
|
||||
// TODO(hs): find a good way to perform this only once.
|
||||
func (o *Options) Validate() error {
|
||||
if oidc := o.GetOIDCOptions(); oidc != nil {
|
||||
if err := oidc.validateAndInitialize(); err != nil {
|
||||
return fmt.Errorf("failed initializing OIDC options: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("no OIDC options available")
|
||||
}
|
||||
|
||||
if dpop := o.GetDPOPOptions(); dpop != nil {
|
||||
if err := dpop.validateAndInitialize(); err != nil {
|
||||
return fmt.Errorf("failed initializing DPoP options: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("no DPoP options available")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package wire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptions_Validate(t *testing.T) {
|
||||
key := []byte(`-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
-----END PUBLIC KEY-----`)
|
||||
|
||||
type fields struct {
|
||||
OIDC *OIDCOptions
|
||||
DPOP *DPOPOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://example.com",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{
|
||||
SigningKey: key,
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "fail/no-oidc-options",
|
||||
fields: fields{
|
||||
OIDC: nil,
|
||||
DPOP: &DPOPOptions{},
|
||||
},
|
||||
expectedErr: errors.New("no OIDC options available"),
|
||||
},
|
||||
{
|
||||
name: "fail/empty-issuer-url",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{},
|
||||
},
|
||||
expectedErr: errors.New("failed initializing OIDC options: either OIDC discovery or issuer URL must be set"),
|
||||
},
|
||||
{
|
||||
name: "fail/invalid-issuer-url",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "\x00",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{},
|
||||
},
|
||||
expectedErr: errors.New(`failed initializing OIDC options: failed creationg OIDC provider config: failed parsing issuer URL: parse "\x00": net/url: invalid control character in URL`),
|
||||
},
|
||||
{
|
||||
name: "fail/issuer-url-template",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://issuer.example.com/{{}",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{},
|
||||
},
|
||||
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC template: template: DeviceID:1: unexpected "}" in command`),
|
||||
},
|
||||
{
|
||||
name: "fail/invalid-transform-template",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://example.com",
|
||||
},
|
||||
Config: &Config{},
|
||||
TransformTemplate: "{{}",
|
||||
},
|
||||
DPOP: &DPOPOptions{
|
||||
SigningKey: key,
|
||||
},
|
||||
},
|
||||
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC transformation template: template: transform:1: unexpected "}" in command`),
|
||||
},
|
||||
{
|
||||
name: "fail/no-dpop-options",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://example.com",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: nil,
|
||||
},
|
||||
expectedErr: errors.New("no DPoP options available"),
|
||||
},
|
||||
{
|
||||
name: "fail/invalid-key",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://example.com",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{
|
||||
SigningKey: []byte{0x00},
|
||||
Target: "",
|
||||
},
|
||||
},
|
||||
expectedErr: errors.New(`failed initializing DPoP options: failed parsing key: error decoding PEM: not a valid PEM encoded block`),
|
||||
},
|
||||
{
|
||||
name: "fail/target-template",
|
||||
fields: fields{
|
||||
OIDC: &OIDCOptions{
|
||||
Provider: &Provider{
|
||||
IssuerURL: "https://example.com",
|
||||
},
|
||||
Config: &Config{},
|
||||
},
|
||||
DPOP: &DPOPOptions{
|
||||
SigningKey: key,
|
||||
Target: "{{}",
|
||||
},
|
||||
},
|
||||
expectedErr: errors.New(`failed initializing DPoP options: failed parsing DPoP template: template: DeviceID:1: unexpected "}" in command`),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &Options{
|
||||
OIDC: tt.fields.OIDC,
|
||||
DPOP: tt.fields.DPOP,
|
||||
}
|
||||
err := o.Validate()
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue