| // Copyright 2015 The Chromium 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 main |
| |
| import ( |
| "encoding/base64" |
| "errors" |
| "fmt" |
| "log" |
| "net/url" |
| "sync" |
| |
| "mojo/public/go/application" |
| "mojo/public/go/bindings" |
| "mojo/public/go/system" |
| "mojo/public/interfaces/network/url_request" |
| auth "mojo/services/authentication/interfaces/authentication" |
| network "mojo/services/network/interfaces/network_service" |
| "mojo/services/network/interfaces/url_loader" |
| vpkg "mojo/services/vanadium/security/interfaces/principal" |
| ) |
| |
| //#include "mojo/public/c/system/handle.h" |
| //#include "mojo/public/c/system/result.h" |
| import "C" |
| |
| const blesserURL = "https://dev.v.io/auth/google/bless" |
| |
| type principalServiceImpl struct { |
| app vpkg.AppInstanceName |
| psd *principalServiceDelegate |
| } |
| |
| func (pImpl *principalServiceImpl) Login() (*vpkg.User, error) { |
| p, err := pImpl.psd.initPrincipal(pImpl.app) |
| if err != nil { |
| return nil, err |
| } |
| |
| token, err := pImpl.psd.getOAuth2Token() |
| if err != nil { |
| return nil, err |
| } |
| |
| b, err := pImpl.psd.getBlessing(token, p.publicKey()) |
| if err != nil { |
| return nil, err |
| } |
| |
| email, err := emailFromBlessing(b) |
| if err != nil { |
| return nil, err |
| } |
| |
| user := vpkg.User{Email: email, Blessing: b} |
| p.addUser(user) |
| return &user, nil |
| } |
| |
| func (pImpl *principalServiceImpl) Logout() (err error) { |
| if p := pImpl.psd.principal(pImpl.app); p != nil { |
| p.clearCurrentUser() |
| } |
| return nil |
| } |
| |
| func (pImpl *principalServiceImpl) GetUser(app *vpkg.AppInstanceName) (*vpkg.User, error) { |
| if app == nil { |
| app = &pImpl.app |
| } |
| p := pImpl.psd.principal(*app) |
| if p == nil { |
| return nil, fmt.Errorf("no principal available for app %v", pImpl.app) |
| } |
| return p.curr, nil |
| } |
| |
| func (pImpl *principalServiceImpl) SetUser(user vpkg.User) (*string, error) { |
| if p := pImpl.psd.principal(pImpl.app); p != nil { |
| return p.setCurrentUser(user), nil |
| } |
| str := fmt.Sprintf("no principal available for app %v; please invoke Login()", pImpl.app) |
| return &str, errors.New(str) |
| } |
| |
| func (pImpl *principalServiceImpl) GetLoggedInUsers() ([]vpkg.User, error) { |
| if p := pImpl.psd.principal(pImpl.app); p != nil { |
| users := p.users() |
| return users, nil |
| } |
| return nil, fmt.Errorf("no principal available for app %v", pImpl.app) |
| } |
| |
| func (pImpl *principalServiceImpl) Create(req vpkg.PrincipalService_Request) { |
| stub := vpkg.NewPrincipalServiceStub(req, pImpl, bindings.GetAsyncWaiter()) |
| pImpl.psd.addStubForCleanup(stub) |
| go func() { |
| for { |
| if err := stub.ServeRequest(); err != nil { |
| connectionError, ok := err.(*bindings.ConnectionError) |
| if !ok || !connectionError.Closed() { |
| log.Println(err) |
| } |
| break |
| } |
| } |
| }() |
| } |
| |
| type principalServiceDelegate struct { |
| Ctx application.Context |
| mu sync.Mutex |
| stubs []*bindings.Stub // GUARDED_BY(mu) |
| appPrincipals map[vpkg.AppInstanceName]*principal // GUARDED_BY(mu) |
| } |
| |
| func (psd *principalServiceDelegate) Initialize(context application.Context) { |
| psd.appPrincipals = make(map[vpkg.AppInstanceName]*principal) |
| psd.Ctx = context |
| } |
| |
| func (psd *principalServiceDelegate) AcceptConnection(connection *application.Connection) { |
| app := vpkg.AppInstanceName{ |
| Url: connection.RequestorURL(), |
| Qualifier: nil, |
| } |
| connection.ProvideServices(&vpkg.PrincipalService_ServiceFactory{&principalServiceImpl{app, psd}}) |
| } |
| |
| func (psd *principalServiceDelegate) addStubForCleanup(stub *bindings.Stub) { |
| psd.mu.Lock() |
| defer psd.mu.Unlock() |
| psd.stubs = append(psd.stubs, stub) |
| } |
| |
| func (psd *principalServiceDelegate) principal(app vpkg.AppInstanceName) *principal { |
| psd.mu.Lock() |
| defer psd.mu.Unlock() |
| return psd.appPrincipals[app] |
| } |
| |
| func (psd *principalServiceDelegate) initPrincipal(app vpkg.AppInstanceName) (*principal, error) { |
| psd.mu.Lock() |
| defer psd.mu.Unlock() |
| if p, ok := psd.appPrincipals[app]; ok { |
| return p, nil |
| } |
| p, err := newPrincipal() |
| if err != nil { |
| return nil, err |
| } |
| psd.appPrincipals[app] = p |
| return p, nil |
| } |
| |
| func (psd *principalServiceDelegate) getOAuth2Token() (string, error) { |
| authReq, authPtr := auth.CreateMessagePipeForAuthenticationService() |
| psd.Ctx.ConnectToApplication("mojo:authentication").ConnectToService(&authReq) |
| authProxy := auth.NewAuthenticationServiceProxy(authPtr, bindings.GetAsyncWaiter()) |
| |
| name, errString, _ := authProxy.SelectAccount(false /*return_last_selected*/) |
| if name == nil { |
| return "", fmt.Errorf("failed to select an account for user:%s", errString) |
| } |
| token, errString, _ := authProxy.GetOAuth2Token(*name, []string{"email"}) |
| if token == nil { |
| return "", fmt.Errorf("failed to obtain OAuth2 token for selected account:%s", errString) |
| } |
| return *token, nil |
| } |
| |
| func (psd *principalServiceDelegate) getBlessing(token string, pub publicKey) ([]uint8, error) { |
| networkReq, networkPtr := network.CreateMessagePipeForNetworkService() |
| psd.Ctx.ConnectToApplication("mojo:network_service").ConnectToService(&networkReq) |
| networkProxy := network.NewNetworkServiceProxy(networkPtr, bindings.GetAsyncWaiter()) |
| |
| urlLoaderReq, urlLoaderPtr := url_loader.CreateMessagePipeForUrlLoader() |
| if err := networkProxy.CreateUrlLoader(urlLoaderReq); err != nil { |
| return nil, fmt.Errorf("failed to create url loader: %v", err) |
| } |
| urlLoader := url_loader.NewUrlLoaderProxy(urlLoaderPtr, bindings.GetAsyncWaiter()) |
| |
| req, err := blessingRequestURL(token, pub) |
| if err != nil { |
| return nil, err |
| } |
| |
| resp, err := urlLoader.Start(*req) |
| if err != nil || resp.Error != nil { |
| return nil, fmt.Errorf("blessings request to Vanadium Identity Provider failed: %v(%v)", err, resp.Error) |
| } |
| |
| res, b := (*resp.Body).ReadData(system.MOJO_READ_DATA_FLAG_ALL_OR_NONE) |
| if res != system.MOJO_RESULT_OK { |
| return nil, fmt.Errorf("failed to read response (blessings) from Vanadium Identity Provider. Result: %v", res) |
| } |
| return b, nil |
| } |
| |
| func (psd *principalServiceDelegate) Quit() { |
| psd.mu.Lock() |
| defer psd.mu.Unlock() |
| for _, stub := range psd.stubs { |
| stub.Close() |
| } |
| } |
| |
| func blessingRequestURL(token string, pub publicKey) (*url_request.UrlRequest, error) { |
| baseURL, err := url.Parse(blesserURL) |
| if err != nil { |
| return nil, err |
| } |
| |
| pubBytes, err := pub.MarshalBinary() |
| if err != nil { |
| return nil, err |
| } |
| |
| params := url.Values{} |
| params.Add("public_key", base64.URLEncoding.EncodeToString(pubBytes)) |
| params.Add("token", token) |
| params.Add("output_format", "json") |
| |
| baseURL.RawQuery = params.Encode() |
| return &url_request.UrlRequest{Url: baseURL.String(), Method: "GET"}, nil |
| } |
| |
| //export MojoMain |
| func MojoMain(handle C.MojoHandle) C.MojoResult { |
| application.Run(&principalServiceDelegate{}, system.MojoHandle(handle)) |
| return C.MOJO_RESULT_OK |
| } |
| |
| func main() { |
| } |