blob: c50ed3a3407c16be11d60c1510de3a7567c5183e [file] [log] [blame]
Asim Shankar61071792014-07-22 13:03:18 -07001package blesser
2
3import (
Asim Shankar7a721752014-08-02 14:27:23 -07004 "encoding/json"
Asim Shankar71061572014-07-22 16:59:18 -07005 "fmt"
Asim Shankar7a721752014-08-02 14:27:23 -07006 "net/http"
Asim Shankar61071792014-07-22 13:03:18 -07007 "time"
8
Jiri Simsa519c5072014-09-17 21:37:57 -07009 "veyron.io/veyron/veyron/services/identity"
Jiri Simsa519c5072014-09-17 21:37:57 -070010 "veyron.io/veyron/veyron/services/identity/revocation"
Suharsh Sivakumarfb5cbb72014-08-27 13:14:22 -070011
Jiri Simsa519c5072014-09-17 21:37:57 -070012 "veyron.io/veyron/veyron2/ipc"
13 "veyron.io/veyron/veyron2/security"
Jiri Simsa519c5072014-09-17 21:37:57 -070014 "veyron.io/veyron/veyron2/vlog"
Asim Shankar61071792014-07-22 13:03:18 -070015)
16
Asim Shankar61071792014-07-22 13:03:18 -070017type googleOAuth struct {
Suharsh Sivakumarfb5cbb72014-08-27 13:14:22 -070018 authcodeClient struct{ ID, Secret string }
Srdjan Petrovic89779ad2014-11-03 15:40:36 -080019 accessTokenClients []AccessTokenClient
Suharsh Sivakumarfb5cbb72014-08-27 13:14:22 -070020 duration time.Duration
21 domain string
22 dischargerLocation string
23 revocationManager *revocation.RevocationManager
Asim Shankar7a721752014-08-02 14:27:23 -070024}
25
Srdjan Petrovic89779ad2014-11-03 15:40:36 -080026// AccessTokenClient represents a client of the BlessUsingAccessToken RPCs.
27type AccessTokenClient struct {
28 // Descriptive name of the client.
29 Name string
30 // OAuth Client ID.
31 ClientID string
32}
33
Asim Shankar7a721752014-08-02 14:27:23 -070034// GoogleParams represents all the parameters provided to NewGoogleOAuthBlesserServer
35type GoogleParams struct {
Srdjan Petrovic89779ad2014-11-03 15:40:36 -080036 // The OAuth client IDs and names for the clients of the BlessUsingAccessToken RPCs.
37 AccessTokenClients []AccessTokenClient
Asim Shankar7a721752014-08-02 14:27:23 -070038 // If non-empty, only email addresses from this domain will be blessed.
39 DomainRestriction string
Suharsh Sivakumarfb5cbb72014-08-27 13:14:22 -070040 // The object name of the discharger service. If this is empty then revocation caveats will not be granted.
41 DischargerLocation string
42 // The revocation manager that generates caveats and manages revocation.
43 RevocationManager *revocation.RevocationManager
Asim Shankarb18a44f2014-10-21 20:25:07 -070044 // The duration for which blessings will be valid. (Used iff RevocationManager is nil).
45 BlessingDuration time.Duration
Asim Shankar61071792014-07-22 13:03:18 -070046}
47
48// NewGoogleOAuthBlesserServer provides an identity.OAuthBlesserService that uses authorization
49// codes to obtain the username of a client and provide blessings with that name.
50//
51// For more details, see documentation on Google OAuth 2.0 flows:
52// https://developers.google.com/accounts/docs/OAuth2
Asim Shankar71061572014-07-22 16:59:18 -070053//
54// Blessings generated by this server expire after duration. If domain is non-empty, then blessings
55// are generated only for email addresses from that domain.
Asim Shankar7a721752014-08-02 14:27:23 -070056func NewGoogleOAuthBlesserServer(p GoogleParams) interface{} {
Todd Wang702385a2014-11-07 01:54:08 -080057 return identity.OAuthBlesserServer(&googleOAuth{
Suharsh Sivakumarfb5cbb72014-08-27 13:14:22 -070058 duration: p.BlessingDuration,
59 domain: p.DomainRestriction,
60 dischargerLocation: p.DischargerLocation,
61 revocationManager: p.RevocationManager,
Suharsh Sivakumard308c7e2014-10-03 12:46:50 -070062 accessTokenClients: p.AccessTokenClients,
63 })
Asim Shankar7a721752014-08-02 14:27:23 -070064}
65
Asim Shankarb3a82ba2014-10-29 11:41:27 -070066func (b *googleOAuth) BlessUsingAccessToken(ctx ipc.ServerContext, accesstoken string) (security.WireBlessings, string, error) {
67 var noblessings security.WireBlessings
Srdjan Petrovica1e6ddb2014-09-08 10:18:44 -070068 if len(b.accessTokenClients) == 0 {
Asim Shankarb3a82ba2014-10-29 11:41:27 -070069 return noblessings, "", fmt.Errorf("server not configured for blessing based on access tokens")
Asim Shankar7a721752014-08-02 14:27:23 -070070 }
71 // URL from: https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
72 tokeninfo, err := http.Get("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" + accesstoken)
73 if err != nil {
Asim Shankarb3a82ba2014-10-29 11:41:27 -070074 return noblessings, "", fmt.Errorf("unable to use token: %v", err)
Asim Shankar7a721752014-08-02 14:27:23 -070075 }
76 if tokeninfo.StatusCode != http.StatusOK {
Asim Shankarb3a82ba2014-10-29 11:41:27 -070077 return noblessings, "", fmt.Errorf("unable to verify access token: %v", tokeninfo.StatusCode)
Asim Shankar7a721752014-08-02 14:27:23 -070078 }
79 // tokeninfo contains a JSON-encoded struct
80 var token struct {
81 IssuedTo string `json:"issued_to"`
82 Audience string `json:"audience"`
83 UserID string `json:"user_id"`
84 Scope string `json:"scope"`
85 ExpiresIn int64 `json:"expires_in"`
86 Email string `json:"email"`
87 VerifiedEmail bool `json:"verified_email"`
88 AccessType string `json:"access_type"`
89 }
90 if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
Asim Shankarb3a82ba2014-10-29 11:41:27 -070091 return noblessings, "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
Asim Shankar7a721752014-08-02 14:27:23 -070092 }
Srdjan Petrovic89779ad2014-11-03 15:40:36 -080093 var client AccessTokenClient
Srdjan Petrovica1e6ddb2014-09-08 10:18:44 -070094 audienceMatch := false
95 for _, c := range b.accessTokenClients {
Srdjan Petrovic89779ad2014-11-03 15:40:36 -080096 if token.Audience == c.ClientID {
97 client = c
Srdjan Petrovica1e6ddb2014-09-08 10:18:44 -070098 audienceMatch = true
99 break
100 }
101 }
102 if !audienceMatch {
103 vlog.Infof("Got access token [%+v], wanted one of client ids %v", token, b.accessTokenClients)
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700104 return noblessings, "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
Asim Shankar7a721752014-08-02 14:27:23 -0700105 }
106 if !token.VerifiedEmail {
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700107 return noblessings, "", fmt.Errorf("email not verified")
Asim Shankar7a721752014-08-02 14:27:23 -0700108 }
Srdjan Petrovic89779ad2014-11-03 15:40:36 -0800109 // Append client.Name to the blessing (e.g., "android", "chrome"). Since blessings issued by
110 // this process do not have many caveats on them and typically have a large expiry duration,
111 // we append this suffix so that servers can explicitly distinguish these clients while
112 // specifying authorization policies (say, via ACLs).
113 return b.bless(ctx, token.Email+security.ChainSeparator+client.Name)
Asim Shankar7a721752014-08-02 14:27:23 -0700114}
115
Asim Shankarb18a44f2014-10-21 20:25:07 -0700116func (b *googleOAuth) bless(ctx ipc.ServerContext, extension string) (security.WireBlessings, string, error) {
117 var noblessings security.WireBlessings
118 self := ctx.LocalPrincipal()
Asim Shankarb3a82ba2014-10-29 11:41:27 -0700119 if self == nil {
120 return noblessings, "", fmt.Errorf("server error: no authentication happened")
121 }
Ankur3c33d422014-10-09 11:53:25 -0700122 var caveat security.Caveat
123 var err error
124 if b.revocationManager != nil {
Suharsh Sivakumar38a1a7b2014-11-17 15:14:38 -0800125 caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
Ankur3c33d422014-10-09 11:53:25 -0700126 } else {
127 caveat, err = security.ExpiryCaveat(time.Now().Add(b.duration))
128 }
129 if err != nil {
Asim Shankarb18a44f2014-10-21 20:25:07 -0700130 return noblessings, "", err
Ankur3c33d422014-10-09 11:53:25 -0700131 }
Asim Shankarb18a44f2014-10-21 20:25:07 -0700132 blessing, err := self.Bless(ctx.RemoteBlessings().PublicKey(), ctx.LocalBlessings(), extension, caveat)
Ankur3c33d422014-10-09 11:53:25 -0700133 if err != nil {
Asim Shankarb18a44f2014-10-21 20:25:07 -0700134 return noblessings, "", err
Ankur3c33d422014-10-09 11:53:25 -0700135 }
Asim Shankarb18a44f2014-10-21 20:25:07 -0700136 return security.MarshalBlessings(blessing), extension, nil
Ankur3c33d422014-10-09 11:53:25 -0700137}