Commit d935fe8c authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Switch to per-scheme manager timestamp

When parsing irma_configuration, we now check the timestamp of each scheme manager to decide if that manager must be copied out of the assets into storage, instead of the (scheme manager-transcending) file irma_configuration/timestamp.
parent 6fc0cfac
......@@ -8,7 +8,6 @@ import (
"path/filepath"
"regexp"
"strconv"
"time"
"crypto/sha256"
......@@ -90,18 +89,14 @@ func NewConfiguration(path string, assets string) (conf *Configuration, err erro
assets: assets,
}
if err = fs.EnsureDirectoryExists(conf.Path); err != nil {
return nil, err
if conf.assets != "" { // If an assets folder is specified, then it must exist
if err = fs.AssertPathExists(conf.assets); err != nil {
return nil, errors.WrapPrefix(err, "Nonexistent assets folder specified", 0)
}
}
isUpToDate, err := conf.isUpToDate()
if err != nil {
if err = fs.EnsureDirectoryExists(conf.Path); err != nil {
return nil, err
}
if conf.assets != "" && !isUpToDate {
if err = conf.CopyFromAssets(false); err != nil {
return nil, err
}
}
// Init all maps
conf.clear()
......@@ -124,6 +119,25 @@ func (conf *Configuration) ParseFolder() (err error) {
// Init all maps
conf.clear()
// Copy any new or updated scheme managers out of the assets into storage
if conf.assets != "" {
err = iterateSubfolders(conf.assets, func(dir string) error {
scheme := NewSchemeManagerIdentifier(filepath.Base(dir))
uptodate, err := conf.isUpToDate(scheme)
if err != nil {
return err
}
if !uptodate {
_, err = conf.CopyManagerFromAssets(scheme)
}
return err
})
if err != nil {
return err
}
}
// Parse scheme managers in storage
var mgrerr *SchemeManagerError
err = iterateSubfolders(conf.Path, func(dir string) error {
manager := NewSchemeManager(filepath.Base(dir))
......@@ -154,7 +168,7 @@ func (conf *Configuration) ParseOrRestoreFolder() error {
err := conf.ParseFolder()
var parse bool
for id := range conf.DisabledSchemeManagers {
parse = conf.CopyManagerFromAssets(id)
parse, _ = conf.CopyManagerFromAssets(id)
}
if parse {
return conf.ParseFolder()
......@@ -208,10 +222,11 @@ func (conf *Configuration) ParseSchemeManagerFolder(dir string, manager *SchemeM
return
}
manager.Timestamp, err = readTimestamp(dir + "/timestamp")
if err != nil {
return errors.New("Could not read scheme manager timestamp")
ts, exists, err := readTimestamp(dir + "/timestamp")
if err != nil || !exists {
return errors.WrapPrefix(err, "Could not read scheme manager timestamp", 0)
}
manager.Timestamp = *ts
if manager.XMLVersion < 7 {
manager.Status = SchemeManagerStatusParsingError
......@@ -403,67 +418,37 @@ func (conf *Configuration) Contains(cred CredentialTypeIdentifier) bool {
conf.CredentialTypes[cred] != nil
}
func (conf *Configuration) readTimestamp(path string) (timestamp *time.Time, exists bool, err error) {
filename := filepath.Join(path, "timestamp")
exists, err = fs.PathExists(filename)
if err != nil || !exists {
return
}
bts, err := ioutil.ReadFile(filename)
if err != nil {
return
}
i, err := strconv.ParseInt(string(bts), 10, 64)
if err != nil {
return
}
t := time.Unix(i, 0)
return &t, true, nil
}
func (conf *Configuration) isUpToDate() (bool, error) {
func (conf *Configuration) isUpToDate(scheme SchemeManagerIdentifier) (bool, error) {
if conf.assets == "" {
return true, nil
}
var err error
newTime, exists, err := conf.readTimestamp(conf.assets)
if err != nil {
return false, err
name := scheme.String()
newTime, exists, err := readTimestamp(filepath.Join(conf.assets, name, "timestamp"))
if err != nil || !exists {
return true, errors.WrapPrefix(err, "Could not read asset timestamp of scheme "+name, 0)
}
if !exists {
return false, errors.New("Timestamp in assets irma_configuration not found")
// The storage version of the manager does not need to have a timestamp. If it does not, it is outdated.
oldTime, exists, err := readTimestamp(filepath.Join(conf.Path, name, "timestamp"))
if err != nil {
return true, err
}
// conf.Path does not need to have a timestamp. If it does not, it is outdated
oldTime, exists, err := conf.readTimestamp(conf.Path)
return exists && !newTime.After(*oldTime), err
return exists && !newTime.After(*oldTime), nil
}
// CopyFromAssets recursively copies the directory tree from the assets folder
// into the directory of this Configuration.
func (conf *Configuration) CopyFromAssets(parse bool) error {
func (conf *Configuration) CopyManagerFromAssets(scheme SchemeManagerIdentifier) (bool, error) {
if conf.assets == "" {
return nil
}
if err := fs.CopyDirectory(conf.assets, conf.Path); err != nil {
return err
}
if parse {
return conf.ParseFolder()
return false, nil
}
return nil
}
func (conf *Configuration) CopyManagerFromAssets(managerID SchemeManagerIdentifier) bool {
manager := conf.SchemeManagers[managerID]
if conf.assets == "" {
return false
// Remove old version; we want an exact copy of the assets version
// not a merge of the assets version and the storage version
name := scheme.String()
if err := os.RemoveAll(filepath.Join(conf.Path, name)); err != nil {
return false, err
}
_ = fs.CopyDirectory(
filepath.Join(conf.assets, manager.ID),
filepath.Join(conf.Path, manager.ID),
return true, fs.CopyDirectory(
filepath.Join(conf.assets, name),
filepath.Join(conf.Path, name),
)
return true
}
// DownloadSchemeManager downloads and returns a scheme manager description.xml file
......@@ -858,7 +843,7 @@ func (conf *Configuration) UpdateSchemeManager(id SchemeManagerIdentifier, downl
if err != nil {
return err
}
if !manager.Timestamp.Before(timestamp) {
if !manager.Timestamp.Before(*timestamp) {
return nil
}
......
......@@ -2,11 +2,9 @@ package irma
import (
"encoding/json"
"io/ioutil"
"math/big"
"os"
"path/filepath"
"strconv"
"testing"
"time"
......@@ -38,30 +36,15 @@ func s2big(s string) (r *big.Int) {
}
func TestConfigurationAutocopy(t *testing.T) {
test.SetupTestStorage(t)
path := filepath.Join("testdata", "storage", "test", "irma_configuration")
// Put now in a timestamp in our folder, and check that NewConfiguration
// does not copy irma_configuration out of the assets
now := strconv.FormatInt(time.Now().Unix(), 10)
require.NoError(t, fs.EnsureDirectoryExists(path))
require.NoError(t, ioutil.WriteFile(filepath.Join(path, "timestamp"), []byte(now), 0600))
_, err := NewConfiguration(path, filepath.Join("testdata", "irma_configuration"))
require.NoError(t, err)
files, err := ioutil.ReadDir(path)
require.NoError(t, fs.CopyDirectory(filepath.Join("testdata", "irma_configuration"), path))
conf, err := NewConfiguration(path, filepath.Join("testdata", "irma_configuration_updated"))
require.NoError(t, err)
require.Len(t, files, 1)
test.ClearTestStorage(t)
test.CreateTestStorage(t)
require.NoError(t, conf.ParseFolder())
// Put an old timestamp in our folder, and check that NewConfiguration
// does copy irma_configuration out of the assets
require.NoError(t, fs.EnsureDirectoryExists(path))
require.NoError(t, ioutil.WriteFile(filepath.Join(path, "timestamp"), []byte("1"), 0600))
_, err = NewConfiguration(path, filepath.Join("testdata", "irma_configuration"))
require.NoError(t, err)
require.NoError(t, fs.AssertPathExists(filepath.Join(path, "irma-demo")))
credid := NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
attrid := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")
require.True(t, conf.CredentialTypes[credid].ContainsAttribute(attrid))
test.ClearTestStorage(t)
}
......
......@@ -12,6 +12,7 @@ import (
"github.com/bwesterb/go-atum"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"github.com/privacybydesign/irmago/internal/fs"
)
// SessionRequest contains the context and nonce for an IRMA session.
......@@ -359,6 +360,10 @@ func (t Timestamp) Before(u Timestamp) bool {
return time.Time(t).Before(time.Time(u))
}
func (t Timestamp) After(u Timestamp) bool {
return time.Time(t).After(time.Time(u))
}
// MarshalJSON marshals a timestamp.
func (t *Timestamp) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
......@@ -379,16 +384,23 @@ func (t *Timestamp) String() string {
return fmt.Sprint(time.Time(*t).Unix())
}
func readTimestamp(path string) (Timestamp, error) {
func readTimestamp(path string) (*Timestamp, bool, error) {
exists, err := fs.PathExists(path)
if err != nil {
return nil, false, err
}
if !exists {
return nil, false, nil
}
bts, err := ioutil.ReadFile(path)
if err != nil {
return Timestamp(time.Unix(0, 0)), errors.New("Could not read scheme manager timestamp")
return nil, true, errors.New("Could not read scheme manager timestamp")
}
return parseTimestamp(bts)
ts, err := parseTimestamp(bts)
return ts, true, err
}
func parseTimestamp(bts []byte) (Timestamp, error) {
func parseTimestamp(bts []byte) (*Timestamp, error) {
// Remove final character \n if present
if bts[len(bts)-1] == '\n' {
bts = bts[:len(bts)-1]
......@@ -396,9 +408,10 @@ func parseTimestamp(bts []byte) (Timestamp, error) {
// convert from byte slice to string; parse as int
str, err := strconv.ParseInt(string(bts), 10, 64)
if err != nil {
return Timestamp(time.Unix(0, 0)), err
return nil, err
}
return Timestamp(time.Unix(str, 0)), nil
ts := Timestamp(time.Unix(str, 0))
return &ts, nil
}
// NewServiceProviderJwt returns a new ServiceProviderJwt.
......
1523108177
\ No newline at end of file
1518279833
\ No newline at end of file
1523108177
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment