diff --git a/cmd/policy/main.go b/cmd/policy/main.go index fbc87fad7d77a8d7a1f176db7d1177a09281ff15..d85ff216f92623938fbc94a6ad0790a883f703c7 100644 --- a/cmd/policy/main.go +++ b/cmd/policy/main.go @@ -74,8 +74,10 @@ func main() { // register rego extension functions { cacheFuncs := regofunc.NewCacheFuncs(cfg.Cache.Addr, httpClient()) + didResolverFuncs := regofunc.NewDIDResolverFuncs(cfg.DIDResolver.Addr, httpClient()) regofunc.Register("cacheGet", rego.Function3(cacheFuncs.CacheGetFunc())) regofunc.Register("cacheSet", rego.Function4(cacheFuncs.CacheSetFunc())) + regofunc.Register("didResolve", rego.Function1(didResolverFuncs.Resolve())) regofunc.Register("strictBuiltinErrors", rego.StrictBuiltinErrors(true)) } diff --git a/internal/config/config.go b/internal/config/config.go index f9119f67420ba434f36949e3ddbb1380dae39595..9a52c10a1bdbbe1e7a668326fb5e77cea00015d0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,9 +3,10 @@ package config import "time" type Config struct { - HTTP httpConfig - Mongo mongoConfig - Cache cacheConfig + HTTP httpConfig + Mongo mongoConfig + Cache cacheConfig + DIDResolver didResolverConfig LogLevel string `envconfig:"LOG_LEVEL" default:"INFO"` } @@ -22,6 +23,10 @@ type cacheConfig struct { Addr string `envconfig:"CACHE_ADDR" required:"true"` } +type didResolverConfig struct { + Addr string `envconfig:"DID_RESOLVER_ADDR" required:"true"` +} + type mongoConfig struct { Addr string `envconfig:"MONGO_ADDR" required:"true"` User string `envconfig:"MONGO_USER" required:"true"` diff --git a/internal/regofunc/did_resolver.go b/internal/regofunc/did_resolver.go new file mode 100644 index 0000000000000000000000000000000000000000..45a7d2645b25d0ff2359a9a3dea0aa5162be1c2b --- /dev/null +++ b/internal/regofunc/did_resolver.go @@ -0,0 +1,60 @@ +package regofunc + +import ( + "fmt" + "net/http" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" + + "code.vereign.com/gaiax/tsa/golib/errors" +) + +type DIDResolverFuncs struct { + resolverAddr string + httpClient *http.Client +} + +func NewDIDResolverFuncs(resolverAddr string, httpClient *http.Client) *DIDResolverFuncs { + return &DIDResolverFuncs{ + resolverAddr: resolverAddr, + httpClient: httpClient, + } +} + +func (dr *DIDResolverFuncs) Resolve() (*rego.Function, rego.Builtin1) { + return ®o.Function{ + Name: "did.resolve", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + var DID string + + if err := ast.As(a.Value, &DID); err != nil { + return nil, fmt.Errorf("invalid DID: %s", err) + } + if DID == "" { + return nil, errors.New("DID cannot be empty") + } + + req, err := http.NewRequest("GET", dr.resolverAddr+"/1.0/identifiers/"+DID, nil) + if err != nil { + return nil, err + } + + resp, err := dr.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} diff --git a/internal/regofunc/did_resolver_test.go b/internal/regofunc/did_resolver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cbe3156f8fb3326c6d37ecdb090080b1bdae7bdc --- /dev/null +++ b/internal/regofunc/did_resolver_test.go @@ -0,0 +1,36 @@ +package regofunc_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-policy-agent/opa/rego" + "github.com/stretchr/testify/assert" + + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" +) + +func TestResolveFunc(t *testing.T) { + expected := `{"data":{"@context":"https://w3id.org/did-resolution/v1","didDocument":"document"}}` + resolverSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, expected) + })) + defer resolverSrv.Close() + + DIDResolverFuncs := regofunc.NewDIDResolverFuncs(resolverSrv.URL, http.DefaultClient) + + r := rego.New( + rego.Query(`did.resolve("did:indy:idunion:BDrEcHc8Tb4Lb2VyQZWEDE")`), + rego.Function1(DIDResolverFuncs.Resolve()), + ) + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) +}