blob: 37734679aec831ce86b45e004e17d6dc8e18ce44 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package blesser
import (
"fmt"
"strings"
"time"
"v.io/x/lib/vlog"
"v.io/x/ref/services/identity"
"v.io/x/ref/services/identity/internal/oauth"
"v.io/x/ref/services/identity/internal/revocation"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
)
type oauthBlesser struct {
oauthProvider oauth.OAuthProvider
authcodeClient struct{ ID, Secret string }
accessTokenClients []oauth.AccessTokenClient
duration time.Duration
dischargerLocation string
revocationManager revocation.RevocationManager
}
// OAuthBlesserParams represents all the parameters provided to NewOAuthBlesserServer
type OAuthBlesserParams struct {
// The OAuth provider that must have issued the access tokens accepted by ths service.
OAuthProvider oauth.OAuthProvider
// The OAuth client IDs and names for the clients of the BlessUsingAccessToken RPCs.
AccessTokenClients []oauth.AccessTokenClient
// The object name of the discharger service. If this is empty then revocation caveats will not be granted.
DischargerLocation string
// The revocation manager that generates caveats and manages revocation.
RevocationManager revocation.RevocationManager
// The duration for which blessings will be valid. (Used iff RevocationManager is nil).
BlessingDuration time.Duration
}
// NewOAuthBlesserServer provides an identity.OAuthBlesserService that uses OAuth2
// access tokens to obtain the username of a client and provide blessings with that
// name.
//
// Blessings generated by this service carry a third-party revocation caveat if a
// RevocationManager is specified by the params or they carry an ExpiryCaveat that
// expires after the duration specified by the params.
func NewOAuthBlesserServer(p OAuthBlesserParams) identity.OAuthBlesserServerStub {
return identity.OAuthBlesserServer(&oauthBlesser{
oauthProvider: p.OAuthProvider,
duration: p.BlessingDuration,
dischargerLocation: p.DischargerLocation,
revocationManager: p.RevocationManager,
accessTokenClients: p.AccessTokenClients,
})
}
func (b *oauthBlesser) BlessUsingAccessToken(ctx *context.T, call rpc.ServerCall, accessToken string) (security.Blessings, string, error) {
// Temporary logging to help debug https://github.com/vanadium/browser/issues/84
// TODO(ashankar): Remove before release!
startBless := time.Now()
defer func() {
if elapsed := time.Since(startBless); elapsed > 10*time.Second {
vlog.Infof("BlessUsingAccessToken took %v for endpoints (%v, %v), PublicKey: %v", elapsed, call.RemoteEndpoint(), call.LocalEndpoint(), call.Security().RemoteBlessings().PublicKey())
}
}()
var noblessings security.Blessings
start := time.Now()
email, clientName, err := b.oauthProvider.GetEmailAndClientName(accessToken, b.accessTokenClients)
if elapsed := time.Since(start); elapsed > 10*time.Second {
vlog.Infof("b.oauthProvider took %v and returned %v for endpoints (%v, %v) PublicKey: %v", elapsed, err, call.RemoteEndpoint(), call.LocalEndpoint(), call.Security().RemoteBlessings().PublicKey())
}
if err != nil {
return noblessings, "", err
}
return b.bless(call.Security(), email, clientName)
}
func (b *oauthBlesser) bless(call security.Call, email, clientName string) (security.Blessings, string, error) {
var noblessings security.Blessings
self := call.LocalPrincipal()
if self == nil {
return noblessings, "", fmt.Errorf("server error: no authentication happened")
}
var caveat security.Caveat
var err error
if b.revocationManager != nil {
start := time.Now()
caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
if elapsed := time.Since(start); elapsed > 10*time.Second {
vlog.Infof("revocationMgr.NewCaveat took %v and returned %v for endpoints (%v, %v) PublicKey: %v", elapsed, err, call.RemoteEndpoint(), call.LocalEndpoint(), call.RemoteBlessings().PublicKey())
}
} else {
caveat, err = security.NewExpiryCaveat(time.Now().Add(b.duration))
}
if err != nil {
return noblessings, "", err
}
// Append clientName (e.g., "android", "chrome") to the email and then bless under that.
// Since blessings issued by this process do not have many caveats on them and typically
// have a large expiry duration, we include the clientName in the extension so that
// servers can explicitly distinguish these clients while specifying authorization policies
// (say, via AccessLists).
extension := strings.Join([]string{email, clientName}, security.ChainSeparator)
blessing, err := self.Bless(call.RemoteBlessings().PublicKey(), call.LocalBlessings(), extension, caveat)
if err != nil {
return noblessings, "", err
}
return blessing, extension, nil
}