// 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 }
// 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, } }