// AllowCapability checks that the user is allowed to perform all the // given operations. If not, the error will be as returned from Allow. // // If AllowCapability succeeds, it returns a list of first party caveat // conditions that must be applied to any macaroon granting capability // to execute the operations. // // If ops contains LoginOp, the user must have been authenticated with a // macaroon associated with the single operation LoginOp only. func (a *Authorizer) AllowCapability(ctxt context.Context, ops []Op) ([]string, error) { nops := 0 for _, op := range ops { if op != LoginOp { nops++ } } if nops == 0 { return nil, errgo.Newf("no non-login operations required in capability") } _, used, err := a.allowAny(ctxt, ops) if err != nil { logger.Infof("allowAny returned used %v; err %v", used, err) return nil, errgo.Mask(err, isDischargeRequiredError) } var squasher caveatSquasher for i, isUsed := range used { if !isUsed { continue } for _, cond := range a.conditions[i] { squasher.add(cond) } } return squasher.final(), nil }
func (a *Authorizer) checkConditions(ctxt context.Context, op Op, conds []string) (map[string]string, error) { logger.Infof("checking conditions %q", conds) declared := checkers.InferDeclaredFromConditions(conds) ctxt = checkers.ContextWithOperations(ctxt, op.Action) ctxt = checkers.ContextWithDeclared(ctxt, declared) for _, cond := range conds { if err := a.service.caveatChecker.CheckFirstPartyCaveat(ctxt, cond); err != nil { return nil, errgo.Mask(err) } } return declared, nil }
func (s *macaroonStore) NewMacaroon(ops []auth.Op, caveats []checkers.Caveat) (*macaroon.Macaroon, error) { rootKey, id, err := s.store.RootKey() if err != nil { return nil, errgo.Mask(err) } mid := macaroonId{ Id: id, Ops: ops, } data, _ := json.Marshal(mid) m, err := macaroon.New(rootKey, data, "", macaroon.LatestVersion) if err != nil { return nil, errgo.Mask(err) } for _, cav := range caveats { if err := bakery.AddCaveat(s.key, s.locator, m, cav); err != nil { return nil, errgo.Notef(err, "cannot add caveat") } } return m, nil }
// verifyIgnoringCaveats verifies the given macaroon and its discharges without // checking any caveats. It returns all the caveats that should // have been checked. func verifyIgnoringCaveats(ms macaroon.Slice, rootKey []byte) ([]string, error) { var caveats []string if len(ms) == 0 { return nil, errgo.New("no macaroons in slice") } if err := ms[0].Verify(rootKey, func(caveat string) error { caveats = append(caveats, caveat) return nil }, ms[1:]); err != nil { return nil, errgo.Mask(err) } return caveats, nil }
func (a *aclUserChecker) Allow(ctxt context.Context, id auth.Identity, ops []auth.Op) (allowed []bool, caveats []checkers.Caveat, err error) { defer func() { logger.Infof("aclUserChecker.Allow(id %#v, ops %#v -> %v, %v, %v", id, ops, allowed, caveats, err) }() u, ok := id.(idmclient.ACLUser) if id != nil && !ok { logger.Infof("warning: user %T is not ACLUser", id) } allowed = make([]bool, len(ops)) var allCaveats []checkers.Caveat for i, op := range ops { acl, caveats, err := a.aclGetter.GetACL(ctxt, op) if err != nil { return nil, nil, errgo.Mask(err) } allCaveats = append(allCaveats, caveats...) ok, err := allowUser(u, acl) allowed[i] = ok } return allowed, allCaveats, nil }
// allowAny is the internal version of AllowAny. Instead of returning an // authInfo struct, it returns a slice describing which operations have // been successfully authorized and a slice describing which macaroons // have been used in the authorization. func (a *Authorizer) allowAny(ctxt context.Context, ops []Op) (authed, used []bool, err error) { if err := a.init(ctxt); err != nil { return nil, nil, errgo.Mask(err) } logger.Infof("after authorizer init, identity %#v", a.identity) used = make([]bool, len(a.macaroons)) authed = make([]bool, len(ops)) numAuthed := 0 for i, op := range ops { if op == LoginOp && len(ops) > 1 { // LoginOp cannot be combined with other operations in the // same macaroon, so ignore it if it is. continue } for _, mindex := range a.authIndexes[op] { _, err := a.checkConditions(ctxt, op, a.conditions[mindex]) if err != nil { logger.Infof("caveat check failed: %v", err) // log error? continue } authed[i] = true numAuthed++ used[mindex] = true break } } if a.identity != nil { // We've authenticated as a user, so even if the operations didn't // specifically require it, we add the authn macaroon and its // conditions to the macaroons used and its con indexes := a.authIndexes[LoginOp] if len(indexes) == 0 { // Should never happen because init ensures it's there. panic("no macaroon info found for login op") } // Note: because we never issue a macaroon which combines LoginOp // with other operations, if the login op macaroon is used, we // know that it's already checked out successfully with LoginOp, // so no need to check again. used[indexes[0]] = true } if numAuthed == len(ops) { // All operations allowed. return nil, used, nil } // There are some unauthorized operations. need := make([]Op, 0, len(ops)-numAuthed) needIndex := make([]int, cap(need)) for i, ok := range authed { if !ok { needIndex[len(need)] = i need = append(need, ops[i]) } } logger.Infof("operations needed after authz macaroons: %#v", need) // Try to authorize the operations even even if we haven't got an authenticated user. oks, caveats, err := a.service.p.UserChecker.Allow(ctxt, a.identity, need) if err != nil { return authed, used, errgo.Notef(err, "cannot check permissions") } if len(oks) != len(need) { return authed, used, errgo.Newf("unexpected slice length returned from Allow (got %d; want %d)", len(oks), len(need)) } stillNeed := make([]Op, 0, len(need)) for i, ok := range oks { if ok { authed[needIndex[i]] = true } else { stillNeed = append(stillNeed, ops[needIndex[i]]) } } if len(stillNeed) == 0 && len(caveats) == 0 { // No more ops need to be authenticated and no caveats to be discharged. return authed, used, nil } logger.Infof("operations still needed after auth check: %#v", stillNeed) if a.identity == nil { // User hasn't authenticated - ask them to do so. return authed, used, &DischargeRequiredError{ Message: "authentication required", Ops: []Op{LoginOp}, Caveats: a.service.p.IdentityClient.IdentityCaveats(), } } if len(caveats) == 0 { return authed, used, ErrPermissionDenied } return authed, used, &DischargeRequiredError{ Message: "some operations have extra caveats", Ops: ops, Caveats: caveats, } }