-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b7b6c65
commit d0dc6fc
Showing
9 changed files
with
350 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package cached | ||
|
||
// The cached package handles a metadata.Provider implementation that both downloads and caches the MDS3 blob. This | ||
// effectively is the recommended provider in most instances as it's fairly robust. Alternatively we suggest implementing | ||
// a similar provider that leverages the memory.Provider as an underlying element. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package cached | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
|
||
"github.com/go-webauthn/webauthn/metadata" | ||
) | ||
|
||
// Option describes an optional pattern for this provider. | ||
type Option func(provider *Provider) (err error) | ||
|
||
// NewFunc describes the type used to create the underlying provider. | ||
type NewFunc func(mds *metadata.Metadata) (provider metadata.Provider, err error) | ||
|
||
// WithPath sets the path name for the cached file. This option is REQUIRED. | ||
func WithPath(name string) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.name = name | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithUpdate is used to enable or disable the update. By default it's set to true. | ||
func WithUpdate(update bool) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.update = update | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithForceUpdate is used to force an update on creation. This will forcibly overwrite the file if possible. | ||
func WithForceUpdate(force bool) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.force = force | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithNew customizes the NewFunc. By default we just create a fairly standard memory.Provider with strict defaults. | ||
func WithNew(newup NewFunc) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.newup = newup | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithDecoder sets the decoder to be used for this provider. By default this is a decoder with the entry parsing errors | ||
// configured to skip that entry. | ||
func WithDecoder(decoder *metadata.Decoder) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.decoder = decoder | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithMetadataURL configures the URL to get the metadata from. This shouldn't be modified unless you know what you're | ||
// doing as we use the metadata.ProductionMDSURL which is safe in most instances. | ||
func WithMetadataURL(uri string) Option { | ||
return func(provider *Provider) (err error) { | ||
if _, err = url.ParseRequestURI(uri); err != nil { | ||
return err | ||
} | ||
|
||
provider.uri = uri | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithClient configures the *http.Client used to get the MDS3 blob. | ||
func WithClient(client *http.Client) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.client = client | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// WithClock allows injection of a metadata.Clock to check the up-to-date status of a blob. | ||
func WithClock(clock metadata.Clock) Option { | ||
return func(provider *Provider) (err error) { | ||
provider.clock = clock | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package cached | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/go-webauthn/webauthn/metadata" | ||
) | ||
|
||
// New returns a new cached Provider given a set of functional Option's. This provider will download a new version and | ||
// save it to the configured file path if it doesn't exist or if it's out of date by default. | ||
func New(opts ...Option) (provider metadata.Provider, err error) { | ||
p := &Provider{ | ||
update: true, | ||
uri: metadata.ProductionMDSURL, | ||
} | ||
|
||
for _, opt := range opts { | ||
if err = opt(p); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if p.name == "" { | ||
return nil, fmt.Errorf("provider configured without setting a path for the cached file blob") | ||
} | ||
|
||
if p.newup == nil { | ||
p.newup = defaultNew | ||
} | ||
|
||
if p.decoder == nil { | ||
if p.decoder, err = metadata.NewDecoder(metadata.WithIgnoreEntryParsingErrors()); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if p.clock == nil { | ||
p.clock = &metadata.RealClock{} | ||
} | ||
|
||
if err = p.init(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return p, nil | ||
} | ||
|
||
// Provider implements a metadata.Provider with a file-based cache. | ||
type Provider struct { | ||
metadata.Provider | ||
|
||
name string | ||
uri string | ||
update bool | ||
force bool | ||
clock metadata.Clock | ||
client *http.Client | ||
decoder *metadata.Decoder | ||
newup NewFunc | ||
} | ||
|
||
func (p *Provider) init() (err error) { | ||
var ( | ||
f *os.File | ||
rc io.ReadCloser | ||
created bool | ||
mds *metadata.Metadata | ||
) | ||
|
||
if f, created, err = doOpenOrCreate(p.name); err != nil { | ||
return err | ||
} | ||
|
||
defer f.Close() | ||
|
||
if created || p.force { | ||
if rc, err = p.get(); err != nil { | ||
return err | ||
} | ||
} else { | ||
if mds, err = p.parse(f); err != nil { | ||
return err | ||
} | ||
|
||
if p.outdated(mds) { | ||
if rc, err = p.get(); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
if rc != nil { | ||
if err = doTruncateCopyAndSeekStart(f, rc); err != nil { | ||
return err | ||
} | ||
|
||
if mds, err = p.parse(f); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
var provider metadata.Provider | ||
|
||
if provider, err = p.newup(mds); err != nil { | ||
return err | ||
} | ||
|
||
p.Provider = provider | ||
|
||
return nil | ||
} | ||
|
||
func (p *Provider) parse(rc io.ReadCloser) (data *metadata.Metadata, err error) { | ||
var payload *metadata.MetadataBLOBPayloadJSON | ||
|
||
if payload, err = p.decoder.Decode(rc); err != nil { | ||
return nil, err | ||
} | ||
|
||
if data, err = p.decoder.Parse(payload); err != nil { | ||
return nil, err | ||
} | ||
|
||
return data, nil | ||
} | ||
|
||
func (p *Provider) outdated(mds *metadata.Metadata) bool { | ||
return p.update && p.clock.Now().After(mds.Parsed.NextUpdate) | ||
} | ||
|
||
func (p *Provider) get() (f io.ReadCloser, err error) { | ||
if p.client == nil { | ||
p.client = &http.Client{} | ||
} | ||
|
||
var res *http.Response | ||
|
||
if res, err = p.client.Get(p.uri); err != nil { | ||
return nil, err | ||
} | ||
|
||
return res.Body, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package cached | ||
|
||
import ( | ||
"github.com/go-webauthn/webauthn/metadata" | ||
"github.com/go-webauthn/webauthn/metadata/providers/memory" | ||
"io" | ||
"os" | ||
) | ||
|
||
func doTruncateCopyAndSeekStart(f *os.File, rc io.ReadCloser) (err error) { | ||
if err = f.Truncate(0); err != nil { | ||
return err | ||
} | ||
|
||
if _, err = io.Copy(f, rc); err != nil { | ||
return err | ||
} | ||
|
||
if _, err = f.Seek(0, io.SeekStart); err != nil { | ||
return err | ||
} | ||
|
||
return rc.Close() | ||
} | ||
|
||
func doOpenOrCreate(name string) (f *os.File, created bool, err error) { | ||
if f, err = os.Open(name); err == nil { | ||
return f, false, nil | ||
} | ||
|
||
if os.IsNotExist(err) { | ||
if f, err = os.Create(name); err != nil { | ||
return nil, false, err | ||
} | ||
|
||
return f, true, nil | ||
} | ||
|
||
return nil, false, err | ||
} | ||
|
||
func defaultNew(mds *metadata.Metadata) (provider metadata.Provider, err error) { | ||
return memory.New( | ||
memory.WithMetadata(mds.ToMap()), | ||
memory.WithValidateEntry(true), | ||
memory.WithValidateEntryPermitZeroAAGUID(false), | ||
memory.WithValidateTrustAnchor(true), | ||
memory.WithValidateStatus(true), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package memory | ||
|
||
// The memory package handles a metadata.Provider implementation that solely exists in memory. It's intended as a basis | ||
// for other providers and generally not recommended to use directly unless you're implementing your own logic to handle | ||
// the download and potential caching of the MDS3 blob yourself. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.