예제 #1
0
파일: service.go 프로젝트: rogpeppe/misc
// 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
}
예제 #2
0
파일: service.go 프로젝트: rogpeppe/misc
// 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,
	}
}