// NewUser tracks a new authenticated user and saves its details in the state func NewUser(st *state.State, username, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return nil, err } sort.Strings(discharges) authStateData.LastID++ authenticatedUser := UserState{ ID: authStateData.LastID, Username: username, Macaroon: macaroon, Discharges: discharges, StoreMacaroon: macaroon, StoreDischarges: discharges, } authStateData.Users = append(authStateData.Users, authenticatedUser) st.Set("auth", authStateData) return &authenticatedUser, nil }
func checkAliasConflict(st *state.State, snapName, alias string) error { // check against snaps var snapNames map[string]*json.RawMessage err := st.Get("snaps", &snapNames) if err != nil && err != state.ErrNoState { return err } for name := range snapNames { if name == alias || strings.HasPrefix(alias, name+".") { return &aliasConflictError{ Alias: alias, Snap: snapName, Reason: fmt.Sprintf("it conflicts with the command namespace of installed snap %q", name), } } } // check against aliases return checkAgainstEnabledAliases(st, func(otherAlias, otherSnap string) error { if otherAlias == alias { return &aliasConflictError{ Alias: alias, Snap: snapName, Reason: fmt.Sprintf("already enabled for %q", otherSnap), } } return nil }) }
// Apply applies any necessary patches to update the provided state to // conventions required by the current patch level of the system. func Apply(s *state.State) error { var stateLevel int s.Lock() err := s.Get("patch-level", &stateLevel) s.Unlock() if err != nil && err != state.ErrNoState { return err } if stateLevel == Level { // already at right level, nothing to do return nil } if stateLevel > Level { return fmt.Errorf("cannot downgrade: snapd is too old for the current system state (patch level %d)", stateLevel) } level := stateLevel for level < Level { logger.Noticef("Patching system state from level %d to %d", level, level+1) patch := patches[level+1] if patch == nil { return fmt.Errorf("cannot upgrade: snapd is too new for the current system state (patch level %d)", level) } err := applyOne(patch, s, level) if err != nil { logger.Noticef("Cannnot patch: %v", err) return fmt.Errorf("cannot patch system state from level %d to %d: %v", level, level+1, err) } level++ } return nil }
// CheckMacaroon returns the UserState for the given macaroon/discharges credentials func CheckMacaroon(st *state.State, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, ErrInvalidAuth } NextUser: for _, user := range authStateData.Users { if user.Macaroon != macaroon { continue } if len(user.Discharges) != len(discharges) { continue } // sort discharges (stored users' discharges are already sorted) sort.Strings(discharges) for i, d := range user.Discharges { if d != discharges[i] { continue NextUser } } return &user, nil } return nil, ErrInvalidAuth }
// patch1 adds the snap type and the current revision to the snap state. func patch1(s *state.State) error { var stateMap map[string]*snapstate.SnapState err := s.Get("snaps", &stateMap) if err == state.ErrNoState { return nil } if err != nil { return err } for snapName, snapState := range stateMap { seq := snapState.Sequence if len(seq) == 0 { continue } typ := snap.TypeApp snapInfo, err := readInfo(snapName, seq[len(seq)-1]) if err != nil { logger.Noticef("Recording type for snap %q: cannot retrieve info, assuming it's a app: %v", snapName, err) } else { logger.Noticef("Recording type for snap %q: setting to %q", snapName, snapInfo.Type) typ = snapInfo.Type } snapState.SetType(typ) snapState.Current = seq[len(seq)-1].Revision } s.Set("snaps", stateMap) return nil }
// Init initializes an empty state to the current implemented patch level. func Init(s *state.State) { s.Lock() defer s.Unlock() if s.Get("patch-level", new(int)) != state.ErrNoState { panic("internal error: expected empty state, attempting to override patch-level without actual patching") } s.Set("patch-level", Level) }
// patch2: // - migrates SnapSetup.Name to SnapSetup.SideInfo.RealName // - backfills SnapState.{Sequence,Candidate}.RealName if its missing func patch2(s *state.State) error { var stateMap map[string]*OldSnapState err := s.Get("snaps", &stateMap) if err == state.ErrNoState { return nil } if err != nil { return err } // migrate SnapSetup in all tasks: // - the new SnapSetup uses SideInfo, backfil from Candidate // - also move SnapSetup.{Name,Revision} into SnapSetup.SideInfo.{RealName,Revision} var oldSS OldSnapSetup var newSS snapstate.SnapSetup for _, t := range s.Tasks() { err := t.Get("snap-setup", &oldSS) if err == state.ErrNoState { continue } if err != nil && err != state.ErrNoState { return err } // some things stay the same newSS.Channel = oldSS.Channel newSS.Flags = oldSS.Flags newSS.SnapPath = oldSS.SnapPath newSS.DownloadInfo = oldSS.DownloadInfo newSS.SideInfo = oldSS.SideInfo // ... and some change if newSS.SideInfo == nil { newSS.SideInfo = &snap.SideInfo{} if snapst, ok := stateMap[oldSS.Name]; ok && snapst.Candidate != nil { newSS.SideInfo = snapst.Candidate } } if newSS.SideInfo.RealName == "" { newSS.SideInfo.RealName = oldSS.Name } if newSS.SideInfo.Revision.Unset() { newSS.SideInfo.Revision = oldSS.Revision } t.Set("snap-setup", &newSS) } // backfill snapstate.SnapState.{Sequence,Candidate} with RealName // (if that is missing, was missing for e.g. sideloaded snaps) for snapName, snapState := range stateMap { for _, si := range snapState.Sequence { setRealName(si, snapName) } } s.Set("snaps", stateMap) return nil }
// CheckMacaroon returns the UserState for the given macaroon/discharges credentials func CheckMacaroon(st *state.State, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, ErrInvalidAuth } snapdMacaroon, err := MacaroonDeserialize(macaroon) if err != nil { return nil, ErrInvalidAuth } // attempt snapd macaroon verification if snapdMacaroon.Location() == snapdMacaroonLocation { // no caveats to check so far check := func(caveat string) error { return nil } // ignoring discharges, unused for snapd macaroons atm err = snapdMacaroon.Verify(authStateData.MacaroonKey, check, nil) if err != nil { return nil, ErrInvalidAuth } macaroonID := snapdMacaroon.Id() userID, err := strconv.Atoi(macaroonID) if err != nil { return nil, ErrInvalidAuth } user, err := User(st, userID) if err != nil { return nil, ErrInvalidAuth } if macaroon != user.Macaroon { return nil, ErrInvalidAuth } return user, nil } // if macaroon is not a snapd macaroon, fallback to previous token-style check NextUser: for _, user := range authStateData.Users { if user.Macaroon != macaroon { continue } if len(user.Discharges) != len(discharges) { continue } // sort discharges (stored users' discharges are already sorted) sort.Strings(discharges) for i, d := range user.Discharges { if d != discharges[i] { continue NextUser } } return &user, nil } return nil, ErrInvalidAuth }
func getConns(st *state.State) (map[string]connState, error) { // Get information about connections from the state var conns map[string]connState err := st.Get("conns", &conns) if err != nil && err != state.ErrNoState { return nil, fmt.Errorf("cannot obtain data about existing connections: %s", err) } if conns == nil { conns = make(map[string]connState) } return conns, nil }
// patch2: // - migrates SnapSetup.Name to SnapSetup.SideInfo.RealName // - backfills SnapState.{Sequence,Candidate}.RealName if its missing func patch2(s *state.State) error { var oldStateMap map[string]*patch1SnapState err := s.Get("snaps", &oldStateMap) if err == state.ErrNoState { return nil } if err != nil { return err } newStateMap := make(map[string]*patch2SnapState, len(oldStateMap)) for key, oldSnapState := range oldStateMap { newStateMap[key] = patch2SnapStateFromPatch1(oldSnapState, key) } // migrate SnapSetup in all tasks: // - the new SnapSetup uses SideInfo, backfil from Candidate // - also move SnapSetup.{Name,Revision} into SnapSetup.SideInfo.{RealName,Revision} var oldSS patch1SnapSetup for _, t := range s.Tasks() { var newSS patch2SnapSetup err := t.Get("snap-setup", &oldSS) if err == state.ErrNoState { continue } if err != nil && err != state.ErrNoState { return err } // some things stay the same newSS.Channel = oldSS.Channel newSS.Flags = patch2Flags(oldSS.Flags) newSS.SnapPath = oldSS.SnapPath // ... and some change newSS.SideInfo = &patch2SideInfo{} if snapst, ok := oldStateMap[oldSS.Name]; ok && snapst.Candidate != nil { newSS.SideInfo = patch2SideInfoFromPatch1(snapst.Candidate, oldSS.Name) } if newSS.SideInfo.RealName == "" { newSS.SideInfo.RealName = oldSS.Name } if newSS.SideInfo.Revision.Unset() { newSS.SideInfo.Revision = oldSS.Revision } t.Set("snap-setup", &newSS) } s.Set("snaps", newStateMap) return nil }
// NewTransaction creates a new configuration transaction initialized with the given state. // // The provided state must be locked by the caller. func NewTransaction(st *state.State) *Transaction { transaction := &Transaction{state: st} transaction.changes = make(map[string]map[string]interface{}) // Record the current state of the map containing the config of every snap // in the system. We'll use it for this transaction. err := st.Get("config", &transaction.pristine) if err == state.ErrNoState { transaction.pristine = make(map[string]map[string]*json.RawMessage) } else if err != nil { panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) } return transaction }
// SetDevice updates the device details in the state. func SetDevice(st *state.State, device *DeviceState) error { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return err } authStateData.Device = device st.Set("auth", authStateData) return nil }
// User returns a user from the state given its ID func User(st *state.State, id int) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return nil, err } for _, user := range authStateData.Users { if user.ID == id { return &user, nil } } return nil, fmt.Errorf("invalid user") }
// All retrieves return a map from name to SnapState for all current snaps in the system state. func All(s *state.State) (map[string]*SnapState, error) { // XXX: result is a map because sideloaded snaps carry no name // atm in their sideinfos var stateMap map[string]*SnapState if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } curStates := make(map[string]*SnapState, len(stateMap)) for snapName, snapState := range stateMap { if snapState.HasCurrent() { curStates[snapName] = snapState } } return curStates, nil }
// Device returns the device details from the state. func Device(st *state.State) (*DeviceState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { return &DeviceState{}, nil } else if err != nil { return nil, err } if authStateData.Device == nil { return &DeviceState{}, nil } return authStateData.Device, nil }
// NewTransaction creates a new config transaction initialized with the given // state. Note that the state should be locked/unlocked by the caller. func NewTransaction(st *state.State) (*Transaction, error) { transaction := &Transaction{state: st} transaction.writeCache = make(systemConfig) // Record the current state of the map containing the config of every snap // in the system. We'll use it for this transaction. if err := st.Get("config", &transaction.config); err != nil { if err != state.ErrNoState { return nil, err } transaction.config = make(systemConfig) } return transaction, nil }
// Get retrieves the SnapState of the given snap. func Get(s *state.State, name string, snapst *SnapState) error { var snaps map[string]*json.RawMessage err := s.Get("snaps", &snaps) if err != nil { return err } raw, ok := snaps[name] if !ok { return state.ErrNoState } err = json.Unmarshal([]byte(*raw), &snapst) if err != nil { return fmt.Errorf("cannot unmarshal snap state: %v", err) } return nil }
// patch6: // - move from a flags-are-ints world to a flags-are-struct-of-bools world func patch6(st *state.State) error { var oldStateMap map[string]*patch4SnapState err := st.Get("snaps", &oldStateMap) if err == state.ErrNoState { return nil } if err != nil { return err } newStateMap := make(map[string]*patch6SnapState, len(oldStateMap)) for key, old := range oldStateMap { newStateMap[key] = &patch6SnapState{ SnapType: old.SnapType, Sequence: old.Sequence, Active: old.Active, Current: old.Current, Channel: old.Channel, patch6Flags: patch6FlagsFromPatch4(old.Flags), } } for _, task := range st.Tasks() { var old patch4SnapSetup err := task.Get("snap-setup", &old) if err == state.ErrNoState { continue } if err != nil && err != state.ErrNoState { return err } task.Set("snap-setup", &patch6SnapSetup{ Channel: old.Channel, UserID: old.UserID, SnapPath: old.SnapPath, DownloadInfo: old.DownloadInfo, SideInfo: old.SideInfo, patch6Flags: patch6FlagsFromPatch4(old.Flags), }) } st.Set("snaps", newStateMap) return nil }
func Users(st *state.State) ([]*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { return nil, nil } if err != nil { return nil, err } users := make([]*UserState, len(authStateData.Users)) for i, _ := range authStateData.Users { users[i] = &authStateData.Users[i] } return users, nil }
func getAliases(st *state.State, snapName string) (map[string]string, error) { var allAliases map[string]*json.RawMessage err := st.Get("aliases", &allAliases) if err != nil { return nil, err } raw := allAliases[snapName] if raw == nil { return nil, state.ErrNoState } var aliases map[string]string err = json.Unmarshal([]byte(*raw), &aliases) if err != nil { return nil, fmt.Errorf("cannot unmarshal snap aliases state: %v", err) } return aliases, nil }
// UpdateUser updates user in state func UpdateUser(st *state.State, user *UserState) error { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return err } for i := range authStateData.Users { if authStateData.Users[i].ID == user.ID { authStateData.Users[i] = *user st.Set("auth", authStateData) return nil } } return fmt.Errorf("invalid user") }
// ActiveInfos returns information about all active snaps. func ActiveInfos(s *state.State) ([]*snap.Info, error) { var stateMap map[string]*SnapState var infos []*snap.Info if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } for snapName, snapState := range stateMap { if !snapState.Active { continue } snapInfo, err := snapState.CurrentInfo() if err != nil { logger.Noticef("cannot retrieve info for snap %q: %s", snapName, err) continue } infos = append(infos, snapInfo) } return infos, nil }
func checkAgainstEnabledAliases(st *state.State, checker func(alias, otherSnap string) error) error { var allAliases map[string]map[string]string err := st.Get("aliases", &allAliases) if err == state.ErrNoState { return nil } if err != nil { return err } for otherSnap, aliasStatuses := range allAliases { for alias, aliasStatus := range aliasStatuses { if aliasStatus == "enabled" { if err := checker(alias, otherSnap); err != nil { return err } } } } return nil }
func infoForType(s *state.State, snapType snap.Type) (*snap.Info, error) { var stateMap map[string]*SnapState if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState { return nil, err } for _, snapState := range stateMap { if !snapState.HasCurrent() { continue } typ, err := snapState.Type() if err != nil { return nil, err } if typ != snapType { continue } return snapState.CurrentInfo() } return nil, state.ErrNoState }
// Set sets the SnapState of the given snap, overwriting any earlier state. func Set(s *state.State, name string, snapst *SnapState) { var snaps map[string]*json.RawMessage err := s.Get("snaps", &snaps) if err != nil && err != state.ErrNoState { panic("internal error: cannot unmarshal snaps state: " + err.Error()) } if snaps == nil { snaps = make(map[string]*json.RawMessage) } if snapst == nil || (len(snapst.Sequence) == 0) { delete(snaps, name) } else { data, err := json.Marshal(snapst) if err != nil { panic("internal error: cannot marshal snap state: " + err.Error()) } raw := json.RawMessage(data) snaps[name] = &raw } s.Set("snaps", snaps) }
func setAliases(st *state.State, snapName string, aliases map[string]string) { var allAliases map[string]*json.RawMessage err := st.Get("aliases", &allAliases) if err != nil && err != state.ErrNoState { panic("internal error: cannot unmarshal snap aliases state: " + err.Error()) } if allAliases == nil { allAliases = make(map[string]*json.RawMessage) } if len(aliases) == 0 { delete(allAliases, snapName) } else { data, err := json.Marshal(aliases) if err != nil { panic("internal error: cannot marshal snap aliases state: " + err.Error()) } raw := json.RawMessage(data) allAliases[snapName] = &raw } st.Set("aliases", allAliases) }
func checkSnapAliasConflict(st *state.State, snapName string) error { var allAliases map[string]map[string]string err := st.Get("aliases", &allAliases) if err == state.ErrNoState { return nil } if err != nil { return err } prefix := fmt.Sprintf("%s.", snapName) for otherSnap, aliasStatuses := range allAliases { for alias, aliasStatus := range aliasStatuses { if aliasStatus == "enabled" { if alias == snapName || strings.HasPrefix(alias, prefix) { return fmt.Errorf("snap %q command namespace conflicts with enabled alias %q for %q", snapName, alias, otherSnap) } } } } return nil }
// NewUser tracks a new authenticated user and saves its details in the state func NewUser(st *state.State, username, email, macaroon string, discharges []string) (*UserState, error) { var authStateData AuthState err := st.Get("auth", &authStateData) if err == state.ErrNoState { authStateData = AuthState{} } else if err != nil { return nil, err } if authStateData.MacaroonKey == nil { authStateData.MacaroonKey, err = generateMacaroonKey() if err != nil { return nil, err } } authStateData.LastID++ localMacaroon, err := newUserMacaroon(authStateData.MacaroonKey, authStateData.LastID) if err != nil { return nil, err } sort.Strings(discharges) authenticatedUser := UserState{ ID: authStateData.LastID, Username: username, Email: email, Macaroon: localMacaroon, Discharges: nil, StoreMacaroon: macaroon, StoreDischarges: discharges, } authStateData.Users = append(authStateData.Users, authenticatedUser) st.Set("auth", authStateData) return &authenticatedUser, nil }
// RemoveUser removes a user from the state given its ID func RemoveUser(st *state.State, userID int) error { var authStateData AuthState err := st.Get("auth", &authStateData) if err != nil { return err } for i := range authStateData.Users { if authStateData.Users[i].ID == userID { // delete without preserving order n := len(authStateData.Users) - 1 authStateData.Users[i] = authStateData.Users[n] authStateData.Users[n] = UserState{} authStateData.Users = authStateData.Users[:n] st.Set("auth", authStateData) return nil } } return fmt.Errorf("invalid user") }
func Patch6StateMap(st *state.State) (map[string]patch6SnapState, error) { var stateMap map[string]patch6SnapState err := st.Get("snaps", &stateMap) return stateMap, err }