Skip to content

Commit

Permalink
feat: did:web resolver should allow resolving DIDs that have differen…
Browse files Browse the repository at this point in the history
…t domain in DID if configured

Limit resolving did:web DIDs to those with hosting domain in DID (or configured additional domains)

Closes trustbloc#1475

Signed-off-by: Sandra Vrtikapa <sandra.vrtikapa@securekey.com>
  • Loading branch information
sandrask committed Sep 15, 2022
1 parent c98aeb3 commit f49b472
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 86 deletions.
24 changes: 24 additions & 0 deletions cmd/orb-server/startcmd/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@ const (
allowedOriginsCacheExpirationFlagUsage = "The expiration time of the allowed origins cache. " +
commonEnvVarUsageText + allowedOriginsCacheExpirationEnvKey

allowedDIDWebDomainsFlagName = "allowed-did-web-domains"
allowedDIDWebDomainsEnvKey = "ALLOWED_DID_WEB_DOMAINS"
allowedDIDWebDomainsFlagUsage = "Allowed domains for did:web method resolution. " + commonEnvVarUsageText + allowedDIDWebDomainsEnvKey

maxWitnessDelayFlagName = "max-witness-delay"
maxWitnessDelayEnvKey = "MAX_WITNESS_DELAY"
maxWitnessDelayFlagShorthand = "w"
Expand Down Expand Up @@ -700,6 +704,7 @@ type orbParameters struct {
methodContext []string
baseEnabled bool
allowedOrigins []string
allowedDomains []string
allowedOriginsCacheExpiration time.Duration
tlsParams *tlsParameters
anchorCredentialParams *anchorCredentialParams
Expand Down Expand Up @@ -756,6 +761,7 @@ type orbParameters struct {
currentSidetreeProtocolVersion string
kmsParams *kmsParameters
requestTokens map[string]string
allowedDIDWebDomains []*url.URL
}

type anchorCredentialParams struct {
Expand Down Expand Up @@ -1204,6 +1210,22 @@ func getOrbParameters(cmd *cobra.Command) (*orbParameters, error) {
return nil, fmt.Errorf("%s: %w", allowedOriginsCacheExpirationFlagName, err)
}

allowedDIDWebDomainsArray, err := cmdutils.GetUserSetVarFromArrayString(cmd, allowedDIDWebDomainsFlagName, allowedDIDWebDomainsEnvKey, true)
if err != nil {
return nil, err
}

var allowedDIDWebDomains []*url.URL

for _, domain := range allowedDIDWebDomainsArray {
domainURL, err := url.Parse(domain)
if err != nil {
return nil, fmt.Errorf("%s: %w", allowedDIDWebDomainsFlagName, err)
}

allowedDIDWebDomains = append(allowedDIDWebDomains, domainURL)
}

dataURIMediaType, err := cmdutils.GetUserSetVarFromString(cmd, dataURIMediaTypeFlagName, dataURIMediaTypeEnvKey, true)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1429,6 +1451,7 @@ func getOrbParameters(cmd *cobra.Command) (*orbParameters, error) {
didAliases: didAliases,
allowedOrigins: allowedOrigins,
allowedOriginsCacheExpiration: allowedOriginsCacheExpiration,
allowedDIDWebDomains: allowedDIDWebDomains,
casType: casType,
ipfsURL: ipfsURL,
localCASReplicateInIPFSEnabled: localCASReplicateInIPFSEnabled,
Expand Down Expand Up @@ -2088,6 +2111,7 @@ func createFlags(startCmd *cobra.Command) {
startCmd.Flags().StringP(didNamespaceFlagName, didNamespaceFlagShorthand, "", didNamespaceFlagUsage)
startCmd.Flags().StringArrayP(didAliasesFlagName, didAliasesFlagShorthand, []string{}, didAliasesFlagUsage)
startCmd.Flags().StringArrayP(allowedOriginsFlagName, allowedOriginsFlagShorthand, []string{}, allowedOriginsFlagUsage)
startCmd.Flags().StringArrayP(allowedDIDWebDomainsFlagName, "", []string{}, allowedDIDWebDomainsFlagUsage)
startCmd.Flags().StringP(anchorCredentialDomainFlagName, anchorCredentialDomainFlagShorthand, "", anchorCredentialDomainFlagUsage)
startCmd.Flags().StringP(anchorCredentialIssuerFlagName, anchorCredentialIssuerFlagShorthand, "", anchorCredentialIssuerFlagUsage)
startCmd.Flags().StringP(anchorCredentialURLFlagName, anchorCredentialURLFlagShorthand, "", anchorCredentialURLFlagUsage)
Expand Down
13 changes: 13 additions & 0 deletions cmd/orb-server/startcmd/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,19 @@ func TestStartCmdWithMissingArg(t *testing.T) {
require.Error(t, err)
require.Contains(t, err.Error(), "allowed-origins-cache-expiration: invalid value [xxx]")
})

t.Run("allowed DID web domains", func(t *testing.T) {
restoreEnv := setEnv(t, allowedDIDWebDomainsEnvKey, ":domain.com")
defer restoreEnv()

startCmd := GetStartCmd()

startCmd.SetArgs(getTestArgs("localhost:8081", "local", "false", databaseTypeMemOption, ""))

err := startCmd.Execute()
require.Error(t, err)
require.Contains(t, err.Error(), "allowed-did-web-domains: parse \":domain.com\": missing protocol scheme")
})
}

func TestStartCmdWithBlankEnvVar(t *testing.T) {
Expand Down
9 changes: 8 additions & 1 deletion cmd/orb-server/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,14 @@ func startOrbServices(parameters *orbParameters) error {
logEndpoint = &noOpRetriever{}
}

webResolveHandler := webresolver.NewResolveHandler(u, parameters.didNamespace,
// current external endpoint is always allowed
allowedDIDWebDomains := []*url.URL{u}

if len(parameters.allowedDIDWebDomains) > 0 {
allowedDIDWebDomains = append(allowedDIDWebDomains, parameters.allowedDIDWebDomains...)
}

webResolveHandler := webresolver.NewResolveHandler(allowedDIDWebDomains, parameters.didNamespace,
unpublishedDIDLabel, orbResolveHandler, metrics.Get())

// create discovery rest api
Expand Down
4 changes: 3 additions & 1 deletion pkg/discovery/endpoint/restapi/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ func (o *Operation) wellKnownHandler(rw http.ResponseWriter, r *http.Request) {
func (o *Operation) orbWebDIDFileHandler(rw http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]

result, err := o.webResolver.ResolveDocument(id)
did := fmt.Sprintf("did:web:%s:scid:%s", o.domainWithPort, id)

result, err := o.webResolver.ResolveDocument(did)
if err != nil {
if errors.Is(err, orberrors.ErrContentNotFound) {
logger.Debugf("web resource[%s] not found", id)
Expand Down
6 changes: 3 additions & 3 deletions pkg/document/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ func GetHint(id, namespace, suffix string) (string, error) {
func BetweenStrings(value, first, second string) (string, error) {
posFirst := strings.Index(value, first)
if posFirst == -1 {
return "", fmt.Errorf("string '%s' doesn't contain first string '%s'", value, first)
return "", fmt.Errorf("string[%s] doesn't contain string[%s]", value, first)
}

posSecond := strings.Index(value, second)
if posSecond == -1 {
return "", fmt.Errorf("string '%s' doesn't contain second string '%s'", value, second)
return "", fmt.Errorf("string[%s] doesn't contain string[%s]", value, second)
}

posFirstAdjusted := posFirst + len(first)
if posFirstAdjusted >= posSecond {
return "", fmt.Errorf("second string '%s' is before first string '%s' in string '%s'", second, first, value)
return "", fmt.Errorf("second string[%s] is before first string[%s] in string[%s]", second, first, value)
}

return value[posFirstAdjusted:posSecond], nil
Expand Down
6 changes: 3 additions & 3 deletions pkg/document/util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,22 @@ func TestBetweenStrings(t *testing.T) {
str, err := BetweenStrings("did:orb:cid:suffix", "first", "suffix")
require.Error(t, err)
require.Empty(t, str)
require.Contains(t, err.Error(), "string 'did:orb:cid:suffix' doesn't contain first string 'first'")
require.Contains(t, err.Error(), "string[did:orb:cid:suffix] doesn't contain string[first]")
})

t.Run("error - doesn't contain second string", func(t *testing.T) {
str, err := BetweenStrings("did:orb:cid:suffix", "cid", "second")
require.Error(t, err)
require.Empty(t, str)
require.Contains(t, err.Error(), "string 'did:orb:cid:suffix' doesn't contain second string 'second'")
require.Contains(t, err.Error(), "string[did:orb:cid:suffix] doesn't contain string[second]")
})

t.Run("error - first string is after second string", func(t *testing.T) {
str, err := BetweenStrings("did:orb:cid:suffix", "suffix", "did:orb")
require.Error(t, err)
require.Empty(t, str)
require.Contains(t, err.Error(),
"second string 'did:orb' is before first string 'suffix' in string 'did:orb:cid:suffix'")
"second string[did:orb] is before first string[suffix] in string[did:orb:cid:suffix]")
})
}

Expand Down
41 changes: 32 additions & 9 deletions pkg/document/webresolver/resolvehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/trustbloc/sidetree-core-go/pkg/document"
"github.com/trustbloc/sidetree-core-go/pkg/docutil"

"github.com/trustbloc/orb/pkg/document/util"
orberrors "github.com/trustbloc/orb/pkg/errors"
diddoctransformer "github.com/trustbloc/orb/pkg/orbclient/doctransformer"
)
Expand All @@ -28,7 +29,7 @@ type ResolveHandler struct {
orbPrefix string
orbUnpublishedDIDLabel string

domain *url.URL
allowedDomains map[string]bool

metrics metricsProvider
}
Expand All @@ -43,10 +44,17 @@ type metricsProvider interface {
}

// NewResolveHandler returns a new document resolve handler.
func NewResolveHandler(domain *url.URL, orbPrefix, orbUnpublishedLabel string, resolver orbResolver,
func NewResolveHandler(domains []*url.URL, orbPrefix, orbUnpublishedLabel string, resolver orbResolver,
metrics metricsProvider) *ResolveHandler {
allowedDomains := make(map[string]bool)

for _, domain := range domains {
domainWithPort := strings.ReplaceAll(domain.Host, ":", "%3A")
allowedDomains[domainWithPort] = true
}

rh := &ResolveHandler{
domain: domain,
allowedDomains: allowedDomains,
orbPrefix: orbPrefix,
orbResolver: resolver,
metrics: metrics,
Expand All @@ -64,8 +72,19 @@ func (r *ResolveHandler) ResolveDocument(id string) (*document.ResolutionResult,
r.metrics.WebDocumentResolveTime(time.Since(startTime))
}()

hostWithPort, err := util.BetweenStrings(id, "did:web:", ":scid:")
if err != nil {
return nil, fmt.Errorf("failed to parse DID: %w", err)
}

if _, ok := r.allowedDomains[hostWithPort]; !ok {
return nil, fmt.Errorf("domain not supported: %s", hostWithPort)
}

suffix := getSuffix(id)

unpublishedOrbDID := r.orbPrefix + docutil.NamespaceDelimiter +
r.orbUnpublishedDIDLabel + docutil.NamespaceDelimiter + id
r.orbUnpublishedDIDLabel + docutil.NamespaceDelimiter + suffix

localResponse, err := r.orbResolver.ResolveDocument(unpublishedOrbDID)
if err != nil {
Expand All @@ -81,11 +100,7 @@ func (r *ResolveHandler) ResolveDocument(id string) (*document.ResolutionResult,
return nil, orberrors.ErrContentNotFound
}

domainWithPort := strings.ReplaceAll(r.domain.Host, ":", "%3A")

webDID := fmt.Sprintf("did:web:%s:scid:%s", domainWithPort, id)

didWebDoc, err := diddoctransformer.WebDocumentFromOrbDocument(webDID, localResponse)
didWebDoc, err := diddoctransformer.WebDocumentFromOrbDocument(id, localResponse)
if err != nil {
return nil, err
}
Expand All @@ -106,3 +121,11 @@ func getDeactivatedFlag(result *document.ResolutionResult) bool {

return false
}

func getSuffix(id string) string {
parts := strings.Split(id, docutil.NamespaceDelimiter)

// suffix is always the last part
// or id if there is no delimiter
return parts[len(parts)-1]
}
Loading

0 comments on commit f49b472

Please sign in to comment.