2015-11-26 18:29:56 +01:00
// -*- Mode: Go; indent-tabs-mode: t -*-
/ *
2022-07-04 11:21:57 +02:00
* Copyright ( C ) 2015 - 2022 Canonical Ltd
2015-11-26 18:29:56 +01:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*
* /
package asserts
import (
2016-09-30 23:26:36 +02:00
"bytes"
2016-07-28 18:30:08 +02:00
"crypto"
2023-02-01 16:04:25 +02:00
"errors"
2016-07-12 11:37:28 +02:00
"fmt"
2015-11-26 18:29:56 +01:00
"time"
2016-07-28 18:30:08 +02:00
2021-06-14 20:54:41 +02:00
// expected for digests
_ "golang.org/x/crypto/sha3"
2016-07-28 18:30:08 +02:00
2016-08-21 14:28:03 +02:00
"github.com/snapcore/snapd/osutil"
2016-07-28 18:30:08 +02:00
"github.com/snapcore/snapd/release"
2019-01-25 09:47:39 +01:00
"github.com/snapcore/snapd/snap/naming"
2022-06-17 16:00:48 +02:00
"github.com/snapcore/snapd/strutil"
2015-11-26 18:29:56 +01:00
)
2016-02-15 19:11:57 +01:00
// SnapDeclaration holds a snap-declaration assertion, declaring a
// snap binding its identifying snap-id to a name, asserting its
// publisher and its other properties.
type SnapDeclaration struct {
assertionBase
2022-06-17 16:00:48 +02:00
refreshControl [ ] string
plugRules map [ string ] * PlugRule
slotRules map [ string ] * SlotRule
autoAliases [ ] string
aliases map [ string ] string
revisionAuthorities [ ] * RevisionAuthority
timestamp time . Time
2016-02-15 19:11:57 +01:00
}
// Series returns the series for which the snap is being declared.
func ( snapdcl * SnapDeclaration ) Series ( ) string {
2016-07-27 15:21:36 +02:00
return snapdcl . HeaderString ( "series" )
2016-02-15 19:11:57 +01:00
}
// SnapID returns the snap id of the declared snap.
func ( snapdcl * SnapDeclaration ) SnapID ( ) string {
2016-07-27 15:21:36 +02:00
return snapdcl . HeaderString ( "snap-id" )
2016-02-15 19:11:57 +01:00
}
// SnapName returns the declared snap name.
func ( snapdcl * SnapDeclaration ) SnapName ( ) string {
2016-07-27 15:21:36 +02:00
return snapdcl . HeaderString ( "snap-name" )
2016-02-15 19:11:57 +01:00
}
// PublisherID returns the identifier of the publisher of the declared snap.
func ( snapdcl * SnapDeclaration ) PublisherID ( ) string {
2016-07-27 15:21:36 +02:00
return snapdcl . HeaderString ( "publisher-id" )
2016-02-15 19:11:57 +01:00
}
// Timestamp returns the time when the snap-declaration was issued.
func ( snapdcl * SnapDeclaration ) Timestamp ( ) time . Time {
return snapdcl . timestamp
}
2016-09-14 11:42:27 +02:00
// RefreshControl returns the ids of snaps whose updates are controlled by this declaration.
2016-09-16 22:33:51 +02:00
func ( snapdcl * SnapDeclaration ) RefreshControl ( ) [ ] string {
return snapdcl . refreshControl
2016-09-02 12:22:50 -03:00
}
2016-09-29 22:09:10 +02:00
// PlugRule returns the plug-side rule about the given interface if one was included in the plugs stanza of the declaration, otherwise it returns nil.
func ( snapdcl * SnapDeclaration ) PlugRule ( interfaceName string ) * PlugRule {
return snapdcl . plugRules [ interfaceName ]
}
2016-09-30 20:15:52 +02:00
// SlotRule returns the slot-side rule about the given interface if one was included in the slots stanza of the declaration, otherwise it returns nil.
func ( snapdcl * SnapDeclaration ) SlotRule ( interfaceName string ) * SlotRule {
return snapdcl . slotRules [ interfaceName ]
}
2016-12-01 19:48:03 +01:00
// AutoAliases returns the optional auto-aliases granted to this snap.
2017-03-17 12:14:13 +01:00
// XXX: deprecated, will go away
2016-12-01 19:48:03 +01:00
func ( snapdcl * SnapDeclaration ) AutoAliases ( ) [ ] string {
return snapdcl . autoAliases
}
2017-03-17 12:14:13 +01:00
// Aliases returns the optional explicit aliases granted to this snap.
func ( snapdcl * SnapDeclaration ) Aliases ( ) map [ string ] string {
return snapdcl . aliases
}
2022-06-17 16:00:48 +02:00
// RevisionAuthority return any revision authority entries matching the given
// provenance.
func ( snapdcl * SnapDeclaration ) RevisionAuthority ( provenance string ) [ ] * RevisionAuthority {
res := make ( [ ] * RevisionAuthority , 0 , 1 )
for _ , ra := range snapdcl . revisionAuthorities {
if strutil . ListContains ( ra . Provenance , provenance ) {
res = append ( res , ra )
}
}
if len ( res ) == 0 {
return nil
}
return res
}
2016-07-12 11:37:28 +02:00
// Implement further consistency checks.
func ( snapdcl * SnapDeclaration ) checkConsistency ( db RODatabase , acck * AccountKey ) error {
2016-07-14 11:50:55 +02:00
if ! db . IsTrustedAccount ( snapdcl . AuthorityID ( ) ) {
2016-07-12 11:37:28 +02:00
return fmt . Errorf ( "snap-declaration assertion for %q (id %q) is not signed by a directly trusted authority: %s" , snapdcl . SnapName ( ) , snapdcl . SnapID ( ) , snapdcl . AuthorityID ( ) )
}
_ , err := db . Find ( AccountType , map [ string ] string {
"account-id" : snapdcl . PublisherID ( ) ,
} )
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2016-07-12 11:37:28 +02:00
return fmt . Errorf ( "snap-declaration assertion for %q (id %q) does not have a matching account assertion for the publisher %q" , snapdcl . SnapName ( ) , snapdcl . SnapID ( ) , snapdcl . PublisherID ( ) )
}
if err != nil {
return err
}
2016-09-02 12:22:50 -03:00
2016-07-12 11:37:28 +02:00
return nil
}
2016-02-15 19:11:57 +01:00
2022-03-14 17:35:33 +01:00
// expected interface is implemented
2016-07-14 11:56:55 +02:00
var _ consistencyChecker = ( * SnapDeclaration ) ( nil )
2016-07-30 20:40:06 +02:00
// Prerequisites returns references to this snap-declaration's prerequisite assertions.
2016-07-15 12:17:49 +02:00
func ( snapdcl * SnapDeclaration ) Prerequisites ( ) [ ] * Ref {
return [ ] * Ref {
2016-11-22 16:10:06 +01:00
{ Type : AccountType , PrimaryKey : [ ] string { snapdcl . PublisherID ( ) } } ,
2016-07-15 12:17:49 +02:00
}
}
2017-02-01 20:56:02 +01:00
func compilePlugRules ( plugs map [ string ] interface { } , compiled func ( iface string , plugRule * PlugRule ) ) error {
for iface , rule := range plugs {
plugRule , err := compilePlugRule ( iface , rule )
if err != nil {
return err
}
compiled ( iface , plugRule )
}
return nil
}
func compileSlotRules ( slots map [ string ] interface { } , compiled func ( iface string , slotRule * SlotRule ) ) error {
for iface , rule := range slots {
slotRule , err := compileSlotRule ( iface , rule )
if err != nil {
return err
}
compiled ( iface , slotRule )
}
return nil
}
2017-01-20 16:55:42 +01:00
func snapDeclarationFormatAnalyze ( headers map [ string ] interface { } , body [ ] byte ) ( formatnum int , err error ) {
_ , plugsOk := headers [ "plugs" ]
_ , slotsOk := headers [ "slots" ]
2017-02-01 20:56:02 +01:00
if ! ( plugsOk || slotsOk ) {
return 0 , nil
2017-01-20 16:55:42 +01:00
}
2018-09-14 13:53:46 +02:00
2017-02-01 20:56:02 +01:00
formatnum = 1
2018-09-14 13:53:46 +02:00
setFormatNum := func ( num int ) {
if num > formatnum {
formatnum = num
}
}
2017-02-01 20:56:02 +01:00
plugs , err := checkMap ( headers , "plugs" )
if err != nil {
return 0 , err
}
err = compilePlugRules ( plugs , func ( _ string , rule * PlugRule ) {
if rule . feature ( dollarAttrConstraintsFeature ) {
2018-09-14 13:53:46 +02:00
setFormatNum ( 2 )
}
if rule . feature ( deviceScopeConstraintsFeature ) {
setFormatNum ( 3 )
2017-02-01 20:56:02 +01:00
}
2019-11-27 17:29:30 +01:00
if rule . feature ( nameConstraintsFeature ) {
setFormatNum ( 4 )
}
2021-05-28 09:44:22 +02:00
if rule . feature ( altAttrMatcherFeature ) {
setFormatNum ( 5 )
}
2017-02-01 20:56:02 +01:00
} )
if err != nil {
return 0 , err
}
slots , err := checkMap ( headers , "slots" )
if err != nil {
return 0 , err
}
err = compileSlotRules ( slots , func ( _ string , rule * SlotRule ) {
if rule . feature ( dollarAttrConstraintsFeature ) {
2018-09-14 13:53:46 +02:00
setFormatNum ( 2 )
}
if rule . feature ( deviceScopeConstraintsFeature ) {
setFormatNum ( 3 )
2017-02-01 20:56:02 +01:00
}
2019-11-27 17:29:30 +01:00
if rule . feature ( nameConstraintsFeature ) {
setFormatNum ( 4 )
}
2021-05-28 09:44:22 +02:00
if rule . feature ( altAttrMatcherFeature ) {
setFormatNum ( 5 )
}
2017-02-01 20:56:02 +01:00
} )
if err != nil {
return 0 , err
}
return formatnum , nil
2017-01-20 16:55:42 +01:00
}
2017-03-17 12:14:13 +01:00
func checkAliases ( headers map [ string ] interface { } ) ( map [ string ] string , error ) {
value , ok := headers [ "aliases" ]
if ! ok {
return nil , nil
}
aliasList , ok := value . ( [ ] interface { } )
if ! ok {
return nil , fmt . Errorf ( ` "aliases" header must be a list of alias maps ` )
}
if len ( aliasList ) == 0 {
return nil , nil
}
aliasMap := make ( map [ string ] string , len ( aliasList ) )
for i , item := range aliasList {
aliasItem , ok := item . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( ` "aliases" header must be a list of alias maps ` )
}
what := fmt . Sprintf ( ` in "aliases" item %d ` , i + 1 )
2019-01-25 09:47:39 +01:00
name , err := checkStringMatchesWhat ( aliasItem , "name" , what , naming . ValidAlias )
2017-03-17 12:14:13 +01:00
if err != nil {
return nil , err
}
what = fmt . Sprintf ( ` for alias %q ` , name )
2019-01-25 09:57:04 +01:00
target , err := checkStringMatchesWhat ( aliasItem , "target" , what , naming . ValidApp )
2017-03-17 12:14:13 +01:00
if err != nil {
return nil , err
}
if _ , ok := aliasMap [ name ] ; ok {
return nil , fmt . Errorf ( ` duplicated definition in "aliases" for alias %q ` , name )
}
aliasMap [ name ] = target
}
return aliasMap , nil
}
2016-12-01 19:48:03 +01:00
2016-02-15 19:11:57 +01:00
func assembleSnapDeclaration ( assert assertionBase ) ( Assertion , error ) {
2016-07-27 15:21:36 +02:00
_ , err := checkExistsString ( assert . headers , "snap-name" )
2016-02-15 19:11:57 +01:00
if err != nil {
return nil , err
}
2016-07-27 15:21:36 +02:00
_ , err = checkNotEmptyString ( assert . headers , "publisher-id" )
2016-02-15 19:11:57 +01:00
if err != nil {
return nil , err
}
2016-09-29 22:09:10 +02:00
timestamp , err := checkRFC3339Date ( assert . headers , "timestamp" )
2016-09-02 12:22:50 -03:00
if err != nil {
return nil , err
}
2016-10-14 14:53:38 +02:00
var refControl [ ] string
var plugRules map [ string ] * PlugRule
var slotRules map [ string ] * SlotRule
2016-10-18 12:01:29 +02:00
refControl , err = checkStringList ( assert . headers , "refresh-control" )
if err != nil {
return nil , err
}
2016-10-14 14:53:38 +02:00
2016-10-18 12:01:29 +02:00
plugs , err := checkMap ( assert . headers , "plugs" )
if err != nil {
return nil , err
}
if plugs != nil {
plugRules = make ( map [ string ] * PlugRule , len ( plugs ) )
2017-02-01 20:56:02 +01:00
err := compilePlugRules ( plugs , func ( iface string , rule * PlugRule ) {
plugRules [ iface ] = rule
} )
if err != nil {
return nil , err
2016-10-14 14:53:38 +02:00
}
2016-10-18 12:01:29 +02:00
}
2016-10-14 14:53:38 +02:00
2016-10-18 12:01:29 +02:00
slots , err := checkMap ( assert . headers , "slots" )
if err != nil {
return nil , err
}
if slots != nil {
slotRules = make ( map [ string ] * SlotRule , len ( slots ) )
2017-02-01 20:56:02 +01:00
err := compileSlotRules ( slots , func ( iface string , rule * SlotRule ) {
slotRules [ iface ] = rule
} )
if err != nil {
return nil , err
2016-10-14 14:53:38 +02:00
}
}
2017-03-17 12:14:13 +01:00
// XXX: depracated, will go away later
2019-01-25 09:47:39 +01:00
autoAliases , err := checkStringListMatches ( assert . headers , "auto-aliases" , naming . ValidAlias )
2016-12-01 19:48:03 +01:00
if err != nil {
return nil , err
}
2017-03-17 12:14:13 +01:00
aliases , err := checkAliases ( assert . headers )
if err != nil {
return nil , err
}
2022-06-17 16:00:48 +02:00
var ras [ ] * RevisionAuthority
ra , ok := assert . headers [ "revision-authority" ]
if ok {
ramaps , ok := ra . ( [ ] interface { } )
if ! ok {
return nil , fmt . Errorf ( "revision-authority stanza must be a list of maps" )
}
if len ( ramaps ) == 0 {
// there is no syntax producing this scenario but be robust
return nil , fmt . Errorf ( "revision-authority stanza cannot be empty" )
}
ras = make ( [ ] * RevisionAuthority , 0 , len ( ramaps ) )
for _ , ramap := range ramaps {
m , ok := ramap . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( "revision-authority stanza must be a list of maps" )
}
accountID , err := checkStringMatchesWhat ( m , "account-id" , "in revision authority" , validAccountID )
if err != nil {
return nil , err
}
2022-07-06 15:33:09 +02:00
prov , err := checkStringListInMap ( m , "provenance" , "provenance in revision authority" , naming . ValidProvenance )
2022-06-17 16:00:48 +02:00
if err != nil {
return nil , err
}
if len ( prov ) == 0 {
return nil , fmt . Errorf ( "provenance in revision authority cannot be empty" )
}
minRevision := 1
maxRevision := 0
if _ , ok := m [ "min-revision" ] ; ok {
var err error
minRevision , err = checkSnapRevisionWhat ( m , "min-revision" , "in revision authority" )
if err != nil {
return nil , err
}
}
if _ , ok := m [ "max-revision" ] ; ok {
var err error
maxRevision , err = checkSnapRevisionWhat ( m , "max-revision" , "in revision authority" )
if err != nil {
return nil , err
}
}
if maxRevision != 0 && maxRevision < minRevision {
2022-06-30 09:42:23 +02:00
return nil , fmt . Errorf ( "optional max-revision cannot be less than min-revision in revision-authority" )
2022-06-17 16:00:48 +02:00
}
devscope , err := compileDeviceScopeConstraint ( m , "revision-authority" )
if err != nil {
return nil , err
}
ras = append ( ras , & RevisionAuthority {
AccountID : accountID ,
Provenance : prov ,
MinRevision : minRevision ,
MaxRevision : maxRevision ,
DeviceScope : devscope ,
} )
}
}
2016-02-15 19:11:57 +01:00
return & SnapDeclaration {
2022-06-17 16:00:48 +02:00
assertionBase : assert ,
refreshControl : refControl ,
plugRules : plugRules ,
slotRules : slotRules ,
autoAliases : autoAliases ,
aliases : aliases ,
revisionAuthorities : ras ,
timestamp : timestamp ,
2016-02-15 19:11:57 +01:00
} , nil
}
2022-06-17 16:00:48 +02:00
// RevisionAuthority holds information about an account that can sign revisions
// for a given snap.
type RevisionAuthority struct {
AccountID string
Provenance [ ] string
MinRevision int
MaxRevision int
DeviceScope * DeviceScopeConstraint
}
// Check tests whether rev matches the revision authority constraints.
2022-07-04 11:21:57 +02:00
// Optional model and store must be provided to cross-check device-specific
// constraints.
func ( ra * RevisionAuthority ) Check ( rev * SnapRevision , model * Model , store * Store ) error {
2022-06-17 16:00:48 +02:00
if ! strutil . ListContains ( ra . Provenance , rev . Provenance ( ) ) {
return fmt . Errorf ( "provenance mismatch" )
}
if rev . AuthorityID ( ) != ra . AccountID {
return fmt . Errorf ( "authority-id mismatch" )
}
revno := rev . SnapRevision ( )
if revno < ra . MinRevision {
return fmt . Errorf ( "snap revision %d is less than min-revision %d" , revno , ra . MinRevision )
}
if ra . MaxRevision != 0 && revno > ra . MaxRevision {
return fmt . Errorf ( "snap revision %d is greater than max-revision %d" , revno , ra . MaxRevision )
}
2022-07-04 11:21:57 +02:00
if ra . DeviceScope != nil && model != nil {
opts := DeviceScopeConstraintCheckOptions { UseFriendlyStores : true }
if err := ra . DeviceScope . Check ( model , store , & opts ) ; err != nil {
return err
}
}
2022-06-17 16:00:48 +02:00
return nil
}
2023-03-22 10:55:25 +07:00
// SnapIntegrity holds information about integrity data included in a revision
// for a given snap.
type SnapIntegrity struct {
SHA3_384 string
Size uint64
}
2016-08-21 14:28:03 +02:00
// SnapFileSHA3_384 computes the SHA3-384 digest of the given snap file.
// It also returns its size.
func SnapFileSHA3_384 ( snapPath string ) ( digest string , size uint64 , err error ) {
sha3_384Dgst , size , err := osutil . FileDigest ( snapPath , crypto . SHA3_384 )
if err != nil {
return "" , 0 , fmt . Errorf ( "cannot compute snap %q digest: %v" , snapPath , err )
}
sha3_384 , err := EncodeDigest ( crypto . SHA3_384 , sha3_384Dgst )
if err != nil {
return "" , 0 , fmt . Errorf ( "cannot encode snap %q digest: %v" , snapPath , err )
}
return sha3_384 , size , nil
}
2016-01-05 17:29:19 +00:00
// SnapBuild holds a snap-build assertion, asserting the properties of a snap
// at the time it was built by the developer.
2015-12-18 13:57:48 +01:00
type SnapBuild struct {
2015-11-30 16:23:39 +01:00
assertionBase
2015-11-26 18:29:56 +01:00
size uint64
timestamp time . Time
}
2016-07-28 18:30:08 +02:00
// SnapSHA3_384 returns the SHA3-384 digest of the snap.
func ( snapbld * SnapBuild ) SnapSHA3_384 ( ) string {
return snapbld . HeaderString ( "snap-sha3-384" )
2016-03-08 11:06:59 +00:00
}
2016-01-05 17:28:55 +00:00
// SnapID returns the snap id of the snap.
2016-02-14 19:32:53 +01:00
func ( snapbld * SnapBuild ) SnapID ( ) string {
2016-07-27 15:21:36 +02:00
return snapbld . HeaderString ( "snap-id" )
2015-11-26 18:29:56 +01:00
}
2016-01-05 17:28:55 +00:00
// SnapSize returns the size of the snap.
2016-02-14 19:32:53 +01:00
func ( snapbld * SnapBuild ) SnapSize ( ) uint64 {
return snapbld . size
2015-11-26 18:29:56 +01:00
}
2016-01-05 17:28:55 +00:00
// Grade returns the grade of the snap: devel|stable
2016-02-14 19:32:53 +01:00
func ( snapbld * SnapBuild ) Grade ( ) string {
2016-07-27 15:21:36 +02:00
return snapbld . HeaderString ( "grade" )
2015-11-26 18:29:56 +01:00
}
2016-01-05 17:29:50 +00:00
// Timestamp returns the time when the snap-build assertion was created.
2016-02-14 19:32:53 +01:00
func ( snapbld * SnapBuild ) Timestamp ( ) time . Time {
return snapbld . timestamp
2015-11-26 18:29:56 +01:00
}
2016-01-07 23:12:14 +01:00
func assembleSnapBuild ( assert assertionBase ) ( Assertion , error ) {
2016-07-28 18:30:08 +02:00
_ , err := checkDigest ( assert . headers , "snap-sha3-384" , crypto . SHA3_384 )
if err != nil {
return nil , err
}
2015-11-26 18:29:56 +01:00
2016-07-28 18:30:08 +02:00
_ , err = checkNotEmptyString ( assert . headers , "snap-id" )
if err != nil {
return nil , err
}
_ , err = checkNotEmptyString ( assert . headers , "grade" )
2015-11-26 18:29:56 +01:00
if err != nil {
return nil , err
}
size , err := checkUint ( assert . headers , "snap-size" , 64 )
if err != nil {
return nil , err
}
timestamp , err := checkRFC3339Date ( assert . headers , "timestamp" )
if err != nil {
return nil , err
}
2015-11-26 19:54:30 +01:00
// ignore extra headers and non-empty body for future compatibility
2015-12-18 13:57:48 +01:00
return & SnapBuild {
2015-11-30 16:23:39 +01:00
assertionBase : assert ,
2015-11-26 18:29:56 +01:00
size : size ,
timestamp : timestamp ,
} , nil
}
2016-01-05 14:52:39 +00:00
// SnapRevision holds a snap-revision assertion, which is a statement by the
2016-03-18 09:55:35 +00:00
// store acknowledging the receipt of a build of a snap and labeling it with a
2016-03-17 10:53:41 +00:00
// snap revision.
2015-12-11 11:37:05 +00:00
type SnapRevision struct {
2015-12-10 13:12:03 +00:00
assertionBase
2016-03-16 12:59:26 +00:00
snapSize uint64
2016-07-15 14:05:36 +02:00
snapRevision int
2016-01-05 15:57:40 +00:00
timestamp time . Time
2023-03-22 10:55:25 +07:00
snapIntegrity * SnapIntegrity
2015-12-10 13:12:03 +00:00
}
2016-07-28 18:30:08 +02:00
// SnapSHA3_384 returns the SHA3-384 digest of the snap.
func ( snaprev * SnapRevision ) SnapSHA3_384 ( ) string {
return snaprev . HeaderString ( "snap-sha3-384" )
2016-03-08 11:57:41 +00:00
}
2022-06-17 13:36:26 +02:00
// Provenance returns the optional provenance of the snap (defaults to
2022-07-06 15:33:09 +02:00
// global-upload (naming.DefaultProvenance)).
2022-06-17 13:36:26 +02:00
func ( snaprev * SnapRevision ) Provenance ( ) string {
return snaprev . HeaderString ( "provenance" )
}
2016-01-05 14:57:13 +00:00
// SnapID returns the snap id of the snap.
2016-02-14 19:32:53 +01:00
func ( snaprev * SnapRevision ) SnapID ( ) string {
2016-07-27 15:21:36 +02:00
return snaprev . HeaderString ( "snap-id" )
2015-12-10 13:12:03 +00:00
}
2016-03-16 12:59:26 +00:00
// SnapSize returns the size in bytes of the snap submitted to the store.
func ( snaprev * SnapRevision ) SnapSize ( ) uint64 {
return snaprev . snapSize
}
2016-03-18 09:55:35 +00:00
// SnapRevision returns the revision assigned to this build of the snap.
2016-07-15 14:05:36 +02:00
func ( snaprev * SnapRevision ) SnapRevision ( ) int {
2016-02-14 19:32:53 +01:00
return snaprev . snapRevision
2015-12-10 13:12:03 +00:00
}
2016-03-18 09:55:35 +00:00
// DeveloperID returns the id of the developer that submitted this build of the
// snap.
2016-02-14 19:32:53 +01:00
func ( snaprev * SnapRevision ) DeveloperID ( ) string {
2016-07-27 15:21:36 +02:00
return snaprev . HeaderString ( "developer-id" )
2015-12-10 13:12:03 +00:00
}
2016-01-05 14:55:57 +00:00
// Timestamp returns the time when the snap-revision was issued.
2016-02-14 19:32:53 +01:00
func ( snaprev * SnapRevision ) Timestamp ( ) time . Time {
return snaprev . timestamp
2015-12-10 13:12:03 +00:00
}
2023-03-22 10:55:25 +07:00
// SnapIntegrity returns the snap integrity data associated with the snap revision if any.
func ( snaprev * SnapRevision ) SnapIntegrity ( ) * SnapIntegrity {
return snaprev . snapIntegrity
}
2015-12-10 13:12:03 +00:00
// Implement further consistency checks.
2016-02-14 19:32:53 +01:00
func ( snaprev * SnapRevision ) checkConsistency ( db RODatabase , acck * AccountKey ) error {
2022-07-06 15:33:09 +02:00
otherProvenance := snaprev . Provenance ( ) != naming . DefaultProvenance
2022-06-17 16:00:48 +02:00
if ! otherProvenance && ! db . IsTrustedAccount ( snaprev . AuthorityID ( ) ) {
// delegating global-upload revisions is not allowed
2016-07-12 11:37:28 +02:00
return fmt . Errorf ( "snap-revision assertion for snap id %q is not signed by a store: %s" , snaprev . SnapID ( ) , snaprev . AuthorityID ( ) )
}
_ , err := db . Find ( AccountType , map [ string ] string {
"account-id" : snaprev . DeveloperID ( ) ,
} )
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2016-07-12 11:37:28 +02:00
return fmt . Errorf ( "snap-revision assertion for snap id %q does not have a matching account assertion for the developer %q" , snaprev . SnapID ( ) , snaprev . DeveloperID ( ) )
}
if err != nil {
return err
}
2022-06-17 16:00:48 +02:00
a , err := db . Find ( SnapDeclarationType , map [ string ] string {
2016-07-29 11:28:14 +02:00
// XXX: mediate getting current series through some context object? this gets the job done for now
2016-07-28 18:30:08 +02:00
"series" : release . Series ,
2016-07-12 11:37:28 +02:00
"snap-id" : snaprev . SnapID ( ) ,
} )
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2016-07-12 11:37:28 +02:00
return fmt . Errorf ( "snap-revision assertion for snap id %q does not have a matching snap-declaration assertion" , snaprev . SnapID ( ) )
}
if err != nil {
return err
}
2022-06-17 16:00:48 +02:00
if otherProvenance {
decl := a . ( * SnapDeclaration )
ras := decl . RevisionAuthority ( snaprev . Provenance ( ) )
2022-06-29 17:31:21 +02:00
matchingRevAuthority := false
2022-06-17 16:00:48 +02:00
for _ , ra := range ras {
2022-07-04 11:21:57 +02:00
// model==store==nil, we do not perform device-specific
// checks at this level, those are performed at
// higher-level guarding installing actual snaps
if err := ra . Check ( snaprev , nil , nil ) ; err == nil {
2022-06-29 17:31:21 +02:00
matchingRevAuthority = true
break
2022-06-17 16:00:48 +02:00
}
}
2022-06-29 17:31:21 +02:00
if ! matchingRevAuthority {
return fmt . Errorf ( "snap-revision assertion with provenance %q for snap id %q is not signed by an authorized authority: %s" , snaprev . Provenance ( ) , snaprev . SnapID ( ) , snaprev . AuthorityID ( ) )
}
2022-06-17 16:00:48 +02:00
}
2015-12-10 13:12:03 +00:00
return nil
}
2022-03-14 17:35:33 +01:00
// expected interface is implemented
2016-02-09 20:39:42 +01:00
var _ consistencyChecker = ( * SnapRevision ) ( nil )
2016-07-30 20:40:06 +02:00
// Prerequisites returns references to this snap-revision's prerequisite assertions.
2016-07-15 12:17:49 +02:00
func ( snaprev * SnapRevision ) Prerequisites ( ) [ ] * Ref {
return [ ] * Ref {
2016-07-30 20:37:20 +02:00
// XXX: mediate getting current series through some context object? this gets the job done for now
2016-11-22 16:10:06 +01:00
{ Type : SnapDeclarationType , PrimaryKey : [ ] string { release . Series , snaprev . SnapID ( ) } } ,
{ Type : AccountType , PrimaryKey : [ ] string { snaprev . DeveloperID ( ) } } ,
2016-07-15 12:17:49 +02:00
}
}
2020-06-11 17:24:25 +02:00
func checkSnapRevisionWhat ( headers map [ string ] interface { } , name , what string ) ( snapRevision int , err error ) {
snapRevision , err = checkIntWhat ( headers , name , what )
if err != nil {
return 0 , err
}
if snapRevision < 1 {
return 0 , fmt . Errorf ( ` %q %s must be >=1: %d ` , name , what , snapRevision )
}
return snapRevision , nil
}
2016-01-07 23:12:14 +01:00
func assembleSnapRevision ( assert assertionBase ) ( Assertion , error ) {
2016-07-28 18:30:08 +02:00
_ , err := checkDigest ( assert . headers , "snap-sha3-384" , crypto . SHA3_384 )
if err != nil {
return nil , err
}
2022-07-06 15:33:09 +02:00
_ , err = checkStringMatches ( assert . headers , "provenance" , naming . ValidProvenance )
2022-06-29 17:41:21 +02:00
if err != nil {
return nil , err
}
2016-07-28 18:30:08 +02:00
_ , err = checkNotEmptyString ( assert . headers , "snap-id" )
if err != nil {
return nil , err
}
2015-12-10 13:12:03 +00:00
2016-03-16 12:59:26 +00:00
snapSize , err := checkUint ( assert . headers , "snap-size" , 64 )
2015-12-10 13:12:03 +00:00
if err != nil {
return nil , err
}
2020-06-11 17:24:25 +02:00
snapRevision , err := checkSnapRevisionWhat ( assert . headers , "snap-revision" , "header" )
2015-12-10 13:12:03 +00:00
if err != nil {
return nil , err
}
2016-07-15 14:05:36 +02:00
2016-07-27 15:21:36 +02:00
_ , err = checkNotEmptyString ( assert . headers , "developer-id" )
2015-12-10 13:12:03 +00:00
if err != nil {
return nil , err
}
timestamp , err := checkRFC3339Date ( assert . headers , "timestamp" )
if err != nil {
return nil , err
}
2023-03-22 10:55:25 +07:00
integrityMap , err := checkMap ( assert . headers , "integrity" )
if err != nil {
return nil , err
}
var snapIntegrity * SnapIntegrity
if integrityMap != nil {
_ , err := checkDigestWhat ( integrityMap , "sha3-384" , crypto . SHA3_384 , "of integrity header" )
if err != nil {
return nil , err
}
size , err := checkUintWhat ( integrityMap , "size" , 64 , "of integrity header" )
if err != nil {
return nil , err
}
snapIntegrity = & SnapIntegrity {
SHA3_384 : integrityMap [ "sha3-384" ] . ( string ) ,
Size : size ,
}
}
2015-12-11 11:37:05 +00:00
return & SnapRevision {
2015-12-10 13:12:03 +00:00
assertionBase : assert ,
2016-03-16 12:59:26 +00:00
snapSize : snapSize ,
2016-01-05 15:57:40 +00:00
snapRevision : snapRevision ,
2015-12-10 13:12:03 +00:00
timestamp : timestamp ,
2023-03-22 10:55:25 +07:00
snapIntegrity : snapIntegrity ,
2015-12-10 13:12:03 +00:00
} , nil
}
2016-08-23 19:10:24 -03:00
2016-09-12 10:04:56 -03:00
// Validation holds a validation assertion, describing that a combination of
// (snap-id, approved-snap-id, approved-revision) has been validated for
// the series, meaning updating to that revision of approved-snap-id
2016-09-13 09:40:39 -03:00
// has been approved by the owner of the gating snap with snap-id.
2016-08-23 19:10:24 -03:00
type Validation struct {
assertionBase
2016-09-13 09:40:39 -03:00
revoked bool
timestamp time . Time
approvedSnapRevision int
2016-08-23 19:10:24 -03:00
}
// Series returns the series for which the validation holds.
func ( validation * Validation ) Series ( ) string {
return validation . HeaderString ( "series" )
}
2016-09-12 10:04:56 -03:00
// SnapID returns the ID of the gating snap.
2016-08-23 19:10:24 -03:00
func ( validation * Validation ) SnapID ( ) string {
return validation . HeaderString ( "snap-id" )
}
2016-09-12 10:06:12 -03:00
// ApprovedSnapID returns the ID of the gated snap.
2016-08-23 19:10:24 -03:00
func ( validation * Validation ) ApprovedSnapID ( ) string {
return validation . HeaderString ( "approved-snap-id" )
}
2016-09-14 10:54:01 +02:00
// ApprovedSnapRevision returns the approved revision of the gated snap.
2016-09-13 09:40:39 -03:00
func ( validation * Validation ) ApprovedSnapRevision ( ) int {
return validation . approvedSnapRevision
2016-08-23 19:10:24 -03:00
}
2016-09-12 10:04:56 -03:00
// Revoked returns true if the validation has been revoked.
func ( validation * Validation ) Revoked ( ) bool {
return validation . revoked
2016-08-23 19:10:24 -03:00
}
2016-09-12 10:04:56 -03:00
// Timestamp returns the time when the validation was issued.
2016-08-23 19:10:24 -03:00
func ( validation * Validation ) Timestamp ( ) time . Time {
return validation . timestamp
}
// Implement further consistency checks.
func ( validation * Validation ) checkConsistency ( db RODatabase , acck * AccountKey ) error {
2016-09-13 09:57:38 -03:00
_ , err := db . Find ( SnapDeclarationType , map [ string ] string {
2016-09-12 10:04:56 -03:00
"series" : validation . Series ( ) ,
2016-09-13 10:42:16 -03:00
"snap-id" : validation . ApprovedSnapID ( ) ,
2016-08-23 19:10:24 -03:00
} )
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2016-09-14 11:15:53 +02:00
return fmt . Errorf ( "validation assertion by snap-id %q does not have a matching snap-declaration assertion for approved-snap-id %q" , validation . SnapID ( ) , validation . ApprovedSnapID ( ) )
2016-08-23 19:10:24 -03:00
}
if err != nil {
return err
}
2016-09-16 22:33:51 +02:00
a , err := db . Find ( SnapDeclarationType , map [ string ] string {
2016-09-12 12:42:00 -03:00
"series" : validation . Series ( ) ,
"snap-id" : validation . SnapID ( ) ,
} )
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2016-09-14 11:15:53 +02:00
return fmt . Errorf ( "validation assertion by snap-id %q does not have a matching snap-declaration assertion" , validation . SnapID ( ) )
2016-09-12 12:42:00 -03:00
}
if err != nil {
return err
}
2016-09-16 22:33:51 +02:00
gatingDecl := a . ( * SnapDeclaration )
if gatingDecl . PublisherID ( ) != validation . AuthorityID ( ) {
return fmt . Errorf ( "validation assertion by snap %q (id %q) not signed by its publisher" , gatingDecl . SnapName ( ) , validation . SnapID ( ) )
}
2016-08-23 19:10:24 -03:00
return nil
}
2022-03-14 17:35:33 +01:00
// expected interface is implemented
2016-08-23 19:10:24 -03:00
var _ consistencyChecker = ( * Validation ) ( nil )
// Prerequisites returns references to this validation's prerequisite assertions.
func ( validation * Validation ) Prerequisites ( ) [ ] * Ref {
return [ ] * Ref {
2016-11-22 16:10:06 +01:00
{ Type : SnapDeclarationType , PrimaryKey : [ ] string { validation . Series ( ) , validation . SnapID ( ) } } ,
{ Type : SnapDeclarationType , PrimaryKey : [ ] string { validation . Series ( ) , validation . ApprovedSnapID ( ) } } ,
2016-08-23 19:10:24 -03:00
}
}
func assembleValidation ( assert assertionBase ) ( Assertion , error ) {
2020-06-11 17:24:25 +02:00
approvedSnapRevision , err := checkSnapRevisionWhat ( assert . headers , "approved-snap-revision" , "header" )
2016-08-23 19:10:24 -03:00
if err != nil {
return nil , err
}
2016-09-14 10:54:01 +02:00
revoked , err := checkOptionalBool ( assert . headers , "revoked" )
2016-08-23 19:10:24 -03:00
if err != nil {
return nil , err
}
timestamp , err := checkRFC3339Date ( assert . headers , "timestamp" )
if err != nil {
return nil , err
}
return & Validation {
2016-09-13 09:40:39 -03:00
assertionBase : assert ,
revoked : revoked ,
timestamp : timestamp ,
approvedSnapRevision : approvedSnapRevision ,
2016-08-23 19:10:24 -03:00
} , nil
}
2016-09-30 23:26:36 +02:00
// BaseDeclaration holds a base-declaration assertion, declaring the
// policies (to start with interface ones) applying to all snaps of
// a series.
type BaseDeclaration struct {
assertionBase
plugRules map [ string ] * PlugRule
slotRules map [ string ] * SlotRule
timestamp time . Time
}
// Series returns the series whose snaps are governed by the declaration.
func ( basedcl * BaseDeclaration ) Series ( ) string {
return basedcl . HeaderString ( "series" )
}
// Timestamp returns the time when the base-declaration was issued.
func ( basedcl * BaseDeclaration ) Timestamp ( ) time . Time {
return basedcl . timestamp
}
// PlugRule returns the plug-side rule about the given interface if one was included in the plugs stanza of the declaration, otherwise it returns nil.
func ( basedcl * BaseDeclaration ) PlugRule ( interfaceName string ) * PlugRule {
return basedcl . plugRules [ interfaceName ]
}
// SlotRule returns the slot-side rule about the given interface if one was included in the slots stanza of the declaration, otherwise it returns nil.
func ( basedcl * BaseDeclaration ) SlotRule ( interfaceName string ) * SlotRule {
return basedcl . slotRules [ interfaceName ]
}
// Implement further consistency checks.
func ( basedcl * BaseDeclaration ) checkConsistency ( db RODatabase , acck * AccountKey ) error {
// XXX: not signed or stored yet in a db, but being ready for that
if ! db . IsTrustedAccount ( basedcl . AuthorityID ( ) ) {
return fmt . Errorf ( "base-declaration assertion for series %s is not signed by a directly trusted authority: %s" , basedcl . Series ( ) , basedcl . AuthorityID ( ) )
}
return nil
}
2022-03-14 17:35:33 +01:00
// expected interface is implemented
2016-09-30 23:26:36 +02:00
var _ consistencyChecker = ( * BaseDeclaration ) ( nil )
func assembleBaseDeclaration ( assert assertionBase ) ( Assertion , error ) {
var plugRules map [ string ] * PlugRule
plugs , err := checkMap ( assert . headers , "plugs" )
if err != nil {
return nil , err
}
if plugs != nil {
plugRules = make ( map [ string ] * PlugRule , len ( plugs ) )
2017-02-01 20:56:02 +01:00
err := compilePlugRules ( plugs , func ( iface string , rule * PlugRule ) {
plugRules [ iface ] = rule
} )
if err != nil {
return nil , err
2016-09-30 23:26:36 +02:00
}
}
var slotRules map [ string ] * SlotRule
slots , err := checkMap ( assert . headers , "slots" )
if err != nil {
return nil , err
}
if slots != nil {
slotRules = make ( map [ string ] * SlotRule , len ( slots ) )
2017-02-01 20:56:02 +01:00
err := compileSlotRules ( slots , func ( iface string , rule * SlotRule ) {
slotRules [ iface ] = rule
} )
if err != nil {
return nil , err
2016-09-30 23:26:36 +02:00
}
}
timestamp , err := checkRFC3339Date ( assert . headers , "timestamp" )
if err != nil {
return nil , err
}
return & BaseDeclaration {
assertionBase : assert ,
plugRules : plugRules ,
slotRules : slotRules ,
timestamp : timestamp ,
} , nil
}
var builtinBaseDeclaration * BaseDeclaration
// BuiltinBaseDeclaration exposes the initialized builtin base-declaration assertion. This is used by overlord/assertstate, other code should use assertstate.BaseDeclaration.
func BuiltinBaseDeclaration ( ) * BaseDeclaration {
return builtinBaseDeclaration
}
var (
builtinBaseDeclarationCheckOrder = [ ] string { "type" , "authority-id" , "series" }
builtinBaseDeclarationExpectedHeaders = map [ string ] interface { } {
"type" : "base-declaration" ,
"authority-id" : "canonical" ,
"series" : release . Series ,
}
)
// InitBuiltinBaseDeclaration initializes the builtin base-declaration based on headers (or resets it if headers is nil).
func InitBuiltinBaseDeclaration ( headers [ ] byte ) error {
if headers == nil {
builtinBaseDeclaration = nil
return nil
}
trimmed := bytes . TrimSpace ( headers )
h , err := parseHeaders ( trimmed )
if err != nil {
return err
}
for _ , name := range builtinBaseDeclarationCheckOrder {
expected := builtinBaseDeclarationExpectedHeaders [ name ]
if h [ name ] != expected {
return fmt . Errorf ( "the builtin base-declaration %q header is not set to expected value %q" , name , expected )
}
}
revision , err := checkRevision ( h )
if err != nil {
return fmt . Errorf ( "cannot assemble the builtin-base declaration: %v" , err )
}
h [ "timestamp" ] = time . Now ( ) . UTC ( ) . Format ( time . RFC3339 )
a , err := assembleBaseDeclaration ( assertionBase {
headers : h ,
body : nil ,
revision : revision ,
content : trimmed ,
signature : [ ] byte ( "$builtin" ) ,
} )
if err != nil {
return fmt . Errorf ( "cannot assemble the builtin base-declaration: %v" , err )
}
builtinBaseDeclaration = a . ( * BaseDeclaration )
return nil
}
2017-03-10 07:30:05 +00:00
type dateRange struct {
Since time . Time
Until time . Time
}
// SnapDeveloper holds a snap-developer assertion, defining the developers who
// can collaborate on a snap while it's owned by a specific publisher.
//
// The primary key (snap-id, publisher-id) allows a snap to have many
// snap-developer assertions, e.g. to allow a future publisher's collaborations
// to be defined before the snap is transferred. However only the
// snap-developer for the current publisher (the snap-declaration publisher-id)
// is relevant to a device.
type SnapDeveloper struct {
assertionBase
developerRanges map [ string ] [ ] * dateRange
}
// SnapID returns the snap id of the snap.
func ( snapdev * SnapDeveloper ) SnapID ( ) string {
return snapdev . HeaderString ( "snap-id" )
}
// PublisherID returns the publisher's account id.
func ( snapdev * SnapDeveloper ) PublisherID ( ) string {
return snapdev . HeaderString ( "publisher-id" )
}
func ( snapdev * SnapDeveloper ) checkConsistency ( db RODatabase , acck * AccountKey ) error {
// Check authority is the publisher or trusted.
authorityID := snapdev . AuthorityID ( )
publisherID := snapdev . PublisherID ( )
if ! db . IsTrustedAccount ( authorityID ) && ( publisherID != authorityID ) {
return fmt . Errorf ( "snap-developer must be signed by the publisher or a trusted authority but got authority %q and publisher %q" , authorityID , publisherID )
}
// Check snap-declaration for the snap-id exists for the series.
// Note: the current publisher is irrelevant here because this assertion
// may be for a future publisher.
_ , err := db . Find ( SnapDeclarationType , map [ string ] string {
// XXX: mediate getting current series through some context object? this gets the job done for now
"series" : release . Series ,
"snap-id" : snapdev . SnapID ( ) ,
} )
2017-07-07 11:24:02 +00:00
if err != nil {
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2017-07-07 11:24:02 +00:00
return fmt . Errorf ( "snap-developer assertion for snap id %q does not have a matching snap-declaration assertion" , snapdev . SnapID ( ) )
}
return err
2017-03-10 07:30:05 +00:00
}
// check there's an account for the publisher-id
_ , err = db . Find ( AccountType , map [ string ] string { "account-id" : publisherID } )
2017-07-07 11:24:02 +00:00
if err != nil {
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2017-07-07 11:24:02 +00:00
return fmt . Errorf ( "snap-developer assertion for snap-id %q does not have a matching account assertion for the publisher %q" , snapdev . SnapID ( ) , publisherID )
}
return err
2017-03-10 07:30:05 +00:00
}
// check there's an account for each developer
for developerID := range snapdev . developerRanges {
if developerID == publisherID {
continue
}
_ , err = db . Find ( AccountType , map [ string ] string { "account-id" : developerID } )
2017-07-07 11:24:02 +00:00
if err != nil {
2023-02-01 16:04:25 +02:00
if errors . Is ( err , & NotFoundError { } ) {
2017-07-07 11:24:02 +00:00
return fmt . Errorf ( "snap-developer assertion for snap-id %q does not have a matching account assertion for the developer %q" , snapdev . SnapID ( ) , developerID )
}
return err
2017-03-10 07:30:05 +00:00
}
}
return nil
}
2022-03-14 17:35:33 +01:00
// expected interface is implemented
2017-03-10 07:30:05 +00:00
var _ consistencyChecker = ( * SnapDeveloper ) ( nil )
// Prerequisites returns references to this snap-developer's prerequisite assertions.
func ( snapdev * SnapDeveloper ) Prerequisites ( ) [ ] * Ref {
// Capacity for the snap-declaration, the publisher and all developers.
refs := make ( [ ] * Ref , 0 , 2 + len ( snapdev . developerRanges ) )
// snap-declaration
// XXX: mediate getting current series through some context object? this gets the job done for now
refs = append ( refs , & Ref { SnapDeclarationType , [ ] string { release . Series , snapdev . SnapID ( ) } } )
// the publisher and developers
publisherID := snapdev . PublisherID ( )
refs = append ( refs , & Ref { AccountType , [ ] string { publisherID } } )
for developerID := range snapdev . developerRanges {
if developerID != publisherID {
refs = append ( refs , & Ref { AccountType , [ ] string { developerID } } )
}
}
return refs
}
func assembleSnapDeveloper ( assert assertionBase ) ( Assertion , error ) {
developerRanges , err := checkDevelopers ( assert . headers )
if err != nil {
return nil , err
}
return & SnapDeveloper {
assertionBase : assert ,
developerRanges : developerRanges ,
} , nil
}
func checkDevelopers ( headers map [ string ] interface { } ) ( map [ string ] [ ] * dateRange , error ) {
value , ok := headers [ "developers" ]
if ! ok {
return nil , nil
}
developers , ok := value . ( [ ] interface { } )
if ! ok {
return nil , fmt . Errorf ( ` "developers" must be a list of developer maps ` )
}
if len ( developers ) == 0 {
return nil , nil
}
2017-03-17 10:47:40 +00:00
// Used to check for a developer with revoking and non-revoking items.
// No entry means developer not yet seen, false means seen but not revoked,
// true means seen and revoked.
revocationStatus := map [ string ] bool { }
2017-03-10 07:30:05 +00:00
developerRanges := make ( map [ string ] [ ] * dateRange )
for i , item := range developers {
developer , ok := item . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( ` "developers" must be a list of developer maps ` )
}
what := fmt . Sprintf ( ` in "developers" item %d ` , i + 1 )
accountID , err := checkStringMatchesWhat ( developer , "developer-id" , what , validAccountID )
if err != nil {
return nil , err
}
what = fmt . Sprintf ( ` in "developers" item %d for developer %q ` , i + 1 , accountID )
since , err := checkRFC3339DateWhat ( developer , "since" , what )
if err != nil {
return nil , err
}
until , err := checkRFC3339DateWithDefaultWhat ( developer , "until" , what , time . Time { } )
if err != nil {
return nil , err
}
if ! until . IsZero ( ) && since . After ( until ) {
return nil , fmt . Errorf ( ` "since" %s must be less than or equal to "until" ` , what )
}
2017-03-17 10:47:40 +00:00
// Track/check for revocation conflicts.
revoked := since . Equal ( until )
previouslyRevoked , ok := revocationStatus [ accountID ]
if ! ok {
revocationStatus [ accountID ] = revoked
2017-03-17 15:15:29 +00:00
} else if previouslyRevoked || revoked {
return nil , fmt . Errorf ( ` revocation for developer %q must be standalone but found other "developers" items ` , accountID )
2017-03-17 10:47:40 +00:00
}
2017-03-10 07:30:05 +00:00
developerRanges [ accountID ] = append ( developerRanges [ accountID ] , & dateRange { since , until } )
}
return developerRanges , nil
}