Skip to content
Snippets Groups Projects
Commit c0f938af authored by Yordan Kinkov's avatar Yordan Kinkov
Browse files

Merge branch main into 41-passwordless-login-ext-functions

parents 4e79f3bd de38bb6b
No related branches found
No related tags found
1 merge request!35Extension functions for GetLoginProofInvitation and GetLoginProofResult"
Pipeline #52818 passed with stages
in 1 minute and 31 seconds
......@@ -84,16 +84,18 @@ func main() {
cacheFuncs := regofunc.NewCacheFuncs(cfg.Cache.Addr, httpClient)
didResolverFuncs := regofunc.NewDIDResolverFuncs(cfg.DIDResolver.Addr, httpClient)
taskFuncs := regofunc.NewTaskFuncs(cfg.Task.Addr, httpClient)
keysFuncs := regofunc.NewPubkeyFuncs(cfg.Signer.Addr, httpClient)
ocmFuncs := regofunc.NewOcmFuncs(cfg.Ocm.Addr, httpClient)
signerFuncs := regofunc.NewSignerFuncs(cfg.Signer.Addr, httpClient)
regofunc.Register("cacheGet", rego.Function3(cacheFuncs.CacheGetFunc()))
regofunc.Register("cacheSet", rego.Function4(cacheFuncs.CacheSetFunc()))
regofunc.Register("didResolve", rego.Function1(didResolverFuncs.ResolveFunc()))
regofunc.Register("taskCreate", rego.Function2(taskFuncs.CreateTaskFunc()))
regofunc.Register("taskListCreate", rego.Function2(taskFuncs.CreateTaskListFunc()))
regofunc.Register("getKey", rego.Function1(keysFuncs.GetKeyFunc()))
regofunc.Register("getAllKeys", rego.FunctionDyn(keysFuncs.GetAllKeysFunc()))
regofunc.Register("issuer", rego.FunctionDyn(keysFuncs.IssuerDID()))
regofunc.Register("getKey", rego.Function1(signerFuncs.GetKeyFunc()))
regofunc.Register("getAllKeys", rego.FunctionDyn(signerFuncs.GetAllKeysFunc()))
regofunc.Register("issuer", rego.FunctionDyn(signerFuncs.IssuerDID()))
regofunc.Register("createProof", rego.Function1(signerFuncs.CreateProof()))
regofunc.Register("verifyProof", rego.Function1(signerFuncs.VerifyProof()))
regofunc.Register("ocmLoginProofInvitation", rego.Function1(ocmFuncs.GetLoginProofInvitation()))
regofunc.Register("ocmLoginProofResult", rego.Function1(ocmFuncs.GetLoginProofResult()))
}
......
......@@ -41,12 +41,12 @@ func (of *OcmFuncs) GetLoginProofInvitation() (*rego.Function, rego.Builtin1) {
type result struct {
Link string `json:"link"`
RequestId string `json:"requestId"`
RequestID string `json:"requestId"`
}
var val ast.Value
val, err = ast.InterfaceToValue(result{
Link: res.Data.PresentationMessage,
RequestId: res.Data.PresentationID,
RequestID: res.Data.PresentationID,
})
if err != nil {
return nil, err
......@@ -63,13 +63,13 @@ func (of *OcmFuncs) GetLoginProofResult() (*rego.Function, rego.Builtin1) {
Memoize: true,
},
func(bctx rego.BuiltinContext, id *ast.Term) (*ast.Term, error) {
var presentationId string
var presentationID string
if err := ast.As(id.Value, &presentationId); err != nil {
if err := ast.As(id.Value, &presentationID); err != nil {
return nil, fmt.Errorf("invalid presentationId: %s", err)
}
res, err := of.client.GetLoginProofResult(bctx.Context, presentationId)
res, err := of.client.GetLoginProofResult(bctx.Context, presentationID)
if err != nil {
return nil, err
}
......
package regofunc
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
......@@ -11,19 +13,19 @@ import (
"github.com/open-policy-agent/opa/types"
)
type PubkeyFuncs struct {
type SignerFuncs struct {
signerAddr string
httpClient *http.Client
}
func NewPubkeyFuncs(signerAddr string, httpClient *http.Client) *PubkeyFuncs {
return &PubkeyFuncs{
func NewSignerFuncs(signerAddr string, httpClient *http.Client) *SignerFuncs {
return &SignerFuncs{
signerAddr: signerAddr,
httpClient: httpClient,
}
}
func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
func (sf *SignerFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
return &rego.Function{
Name: "keys.get",
Decl: types.NewFunction(types.Args(types.S), types.A),
......@@ -39,7 +41,7 @@ func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
return nil, fmt.Errorf("empty keyname")
}
uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys/" + key)
uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys/" + key)
if err != nil {
return nil, err
}
......@@ -49,7 +51,7 @@ func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
return nil, err
}
resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
if err != nil {
return nil, err
}
......@@ -68,14 +70,14 @@ func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
}
}
func (pf *PubkeyFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
func (sf *SignerFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
return &rego.Function{
Name: "keys.getAll",
Decl: types.NewFunction(nil, types.A),
Memoize: true,
},
func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys")
uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys")
if err != nil {
return nil, err
}
......@@ -85,7 +87,7 @@ func (pf *PubkeyFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
return nil, err
}
resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
if err != nil {
return nil, err
}
......@@ -104,14 +106,14 @@ func (pf *PubkeyFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
}
}
func (pf *PubkeyFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
func (sf *SignerFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
return &rego.Function{
Name: "issuer",
Decl: types.NewFunction(nil, types.A),
Memoize: true,
},
func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/issuerDID")
uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/issuerDID")
if err != nil {
return nil, err
}
......@@ -121,7 +123,7 @@ func (pf *PubkeyFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
return nil, err
}
resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
if err != nil {
return nil, err
}
......@@ -139,3 +141,135 @@ func (pf *PubkeyFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
return ast.NewTerm(v), nil
}
}
func (sf *SignerFuncs) CreateProof() (*rego.Function, rego.Builtin1) {
return &rego.Function{
Name: "proof.create",
Decl: types.NewFunction(types.Args(types.S), types.A),
Memoize: true,
},
func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) {
// cred represents verifiable credential or presentation
var cred map[string]interface{}
if err := ast.As(credential.Value, &cred); err != nil {
return nil, fmt.Errorf("invalid credential: %s", err)
}
if cred["type"] == nil {
return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential")
}
credType, ok := cred["type"].(string)
if !ok {
return nil, fmt.Errorf("invalid credential type, string is expected")
}
var createProofPath string
switch credType {
case "VerifiableCredential":
createProofPath = "/v1/credential/proof"
case "VerifiablePresentation":
createProofPath = "/v1/presentation/proof"
default:
return nil, fmt.Errorf("unknown credential type: %q", credType)
}
jsonCred, err := json.Marshal(cred)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", sf.signerAddr+createProofPath, bytes.NewReader(jsonCred))
if err != nil {
return nil, err
}
resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
if err != nil {
return nil, err
}
defer resp.Body.Close() // nolint:errcheck
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
}
v, err := ast.ValueFromReader(resp.Body)
if err != nil {
return nil, err
}
return ast.NewTerm(v), nil
}
}
func (sf *SignerFuncs) VerifyProof() (*rego.Function, rego.Builtin1) {
return &rego.Function{
Name: "proof.verify",
Decl: types.NewFunction(types.Args(types.S), types.A),
Memoize: true,
},
func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) {
// cred represents verifiable credential or presentation
var cred map[string]interface{}
if err := ast.As(credential.Value, &cred); err != nil {
return nil, fmt.Errorf("invalid credential: %s", err)
}
if cred["type"] == nil {
return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential")
}
credType, ok := cred["type"].(string)
if !ok {
return nil, fmt.Errorf("invalid credential type, string is expected")
}
if cred["proof"] == nil {
return nil, fmt.Errorf("credential data does contain proof section")
}
var verifyProofPath string
switch credType {
case "VerifiableCredential":
verifyProofPath = "/v1/credential/verify"
case "VerifiablePresentation":
verifyProofPath = "/v1/presentation/verify"
default:
return nil, fmt.Errorf("unknown credential type: %q", credType)
}
jsonCred, err := json.Marshal(cred)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", sf.signerAddr+verifyProofPath, bytes.NewReader(jsonCred))
if err != nil {
return nil, err
}
resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
if err != nil {
return nil, err
}
defer resp.Body.Close() // nolint:errcheck
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
}
var result struct {
Valid bool `json:"valid"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response from signer: %v", err)
}
if !result.Valid {
return nil, fmt.Errorf("proof is invalid")
}
return ast.NewTerm(ast.Boolean(true)), nil
}
}
......@@ -21,7 +21,7 @@ func TestGetKeyFunc(t *testing.T) {
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
r := rego.New(
rego.Query(`keys.get("key1")`),
rego.Function1(keysFuncs.GetKeyFunc()),
......@@ -41,7 +41,7 @@ func TestGetKeyFuncError(t *testing.T) {
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
r := rego.New(
rego.Query(`keys.get("key1")`),
rego.Function1(keysFuncs.GetKeyFunc()),
......@@ -62,7 +62,7 @@ func TestGetAllKeysFunc(t *testing.T) {
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
r := rego.New(
rego.Query(`keys.getAll()`),
rego.FunctionDyn(keysFuncs.GetAllKeysFunc()),
......@@ -83,7 +83,7 @@ func TestIssuerDID(t *testing.T) {
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
r := rego.New(
rego.Query(`issuer()`),
rego.FunctionDyn(keysFuncs.IssuerDID()),
......@@ -96,3 +96,154 @@ func TestIssuerDID(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expected, string(resultBytes))
}
func TestCreateProof(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
signerResponseCode int
errtext string
}{
{
name: "missing credential type",
input: map[string]interface{}{"vc": "data"},
errtext: "credential data does not specify type",
},
{
name: "unknown credential type",
input: map[string]interface{}{"type": "non-existing-type"},
errtext: "unknown credential type",
},
{
name: "signer returns error for VC",
input: map[string]interface{}{"type": "VerifiableCredential"},
signerResponseCode: http.StatusBadRequest,
errtext: "400 Bad Request",
},
{
name: "signer returns error for VP",
input: map[string]interface{}{"type": "VerifiablePresentation"},
signerResponseCode: http.StatusBadRequest,
errtext: "400 Bad Request",
},
{
name: "signer returns successfully",
input: map[string]interface{}{"type": "VerifiableCredential"},
signerResponseCode: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
expected := `{"vc":"data"}`
signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(test.signerResponseCode)
_, _ = fmt.Fprint(w, expected)
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
query, err := rego.New(
rego.Query(`proof.create(input)`),
rego.Function1(keysFuncs.CreateProof()),
rego.StrictBuiltinErrors(true),
).PrepareForEval(context.Background())
assert.NoError(t, err)
resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input))
if err != nil {
assert.Contains(t, err.Error(), test.errtext)
} else {
assert.NotEmpty(t, resultSet)
assert.NotEmpty(t, resultSet[0].Expressions)
resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
assert.NoError(t, err)
assert.Equal(t, expected, string(resultBytes))
}
})
}
}
func TestVerifyProof(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
signerResponseCode int
errtext string
}{
{
name: "invalid credential",
input: nil,
errtext: "credential data does not specify type",
},
{
name: "missing credential type",
input: map[string]interface{}{"vc": "data"},
errtext: "credential data does not specify type",
},
{
name: "credential type is not string",
input: map[string]interface{}{"type": 123},
signerResponseCode: http.StatusBadRequest,
errtext: "invalid credential type, string is expected",
},
{
name: "missing proof section",
input: map[string]interface{}{"type": "VerifiableCredential"},
errtext: "credential data does contain proof section",
},
{
name: "unknown credential type",
input: map[string]interface{}{"proof": "iamhere", "type": "non-existing-type"},
errtext: "unknown credential type",
},
{
name: "signer returns error for VC",
input: map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"},
signerResponseCode: http.StatusBadRequest,
errtext: "400 Bad Request",
},
{
name: "signer returns error for VP",
input: map[string]interface{}{"proof": "iamhere", "type": "VerifiablePresentation"},
signerResponseCode: http.StatusBadRequest,
errtext: "400 Bad Request",
},
{
name: "signer returns successfully",
input: map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"},
signerResponseCode: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
expected := `{"valid":true}`
signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(test.signerResponseCode)
_, _ = fmt.Fprint(w, expected)
}))
defer signerSrv.Close()
keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
query, err := rego.New(
rego.Query(`proof.verify(input)`),
rego.Function1(keysFuncs.VerifyProof()),
rego.StrictBuiltinErrors(true),
).PrepareForEval(context.Background())
assert.NoError(t, err)
resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input))
if err != nil {
assert.NotEmpty(t, test.errtext, "test case must contain error, but doesn't")
assert.Contains(t, err.Error(), test.errtext)
} else {
assert.NotEmpty(t, resultSet)
assert.NotEmpty(t, resultSet[0].Expressions)
valid, ok := resultSet[0].Expressions[0].Value.(bool)
assert.True(t, ok)
assert.True(t, valid)
}
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment