github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/apiserver/admin.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 stderrors "errors" 8 "sync" 9 10 "launchpad.net/juju-core/errors" 11 "launchpad.net/juju-core/rpc" 12 "launchpad.net/juju-core/state" 13 "launchpad.net/juju-core/state/api/params" 14 "launchpad.net/juju-core/state/apiserver/common" 15 "launchpad.net/juju-core/state/presence" 16 ) 17 18 func newStateServer(srv *Server, rpcConn *rpc.Conn, reqNotifier *requestNotifier) *initialRoot { 19 r := &initialRoot{ 20 srv: srv, 21 rpcConn: rpcConn, 22 } 23 r.admin = &srvAdmin{ 24 root: r, 25 reqNotifier: reqNotifier, 26 } 27 return r 28 } 29 30 // initialRoot implements the API that a client first sees 31 // when connecting to the API. We start serving a different 32 // API once the user has logged in. 33 type initialRoot struct { 34 srv *Server 35 rpcConn *rpc.Conn 36 37 admin *srvAdmin 38 } 39 40 // Admin returns an object that provides API access 41 // to methods that can be called even when not 42 // authenticated. 43 func (r *initialRoot) Admin(id string) (*srvAdmin, error) { 44 if id != "" { 45 // Safeguard id for possible future use. 46 return nil, common.ErrBadId 47 } 48 return r.admin, nil 49 } 50 51 // srvAdmin is the only object that unlogged-in 52 // clients can access. It holds any methods 53 // that are needed to log in. 54 type srvAdmin struct { 55 mu sync.Mutex 56 root *initialRoot 57 loggedIn bool 58 reqNotifier *requestNotifier 59 } 60 61 var errAlreadyLoggedIn = stderrors.New("already logged in") 62 63 // Login logs in with the provided credentials. 64 // All subsequent requests on the connection will 65 // act as the authenticated user. 66 func (a *srvAdmin) Login(c params.Creds) error { 67 a.mu.Lock() 68 defer a.mu.Unlock() 69 if a.loggedIn { 70 // This can only happen if Login is called concurrently. 71 return errAlreadyLoggedIn 72 } 73 entity, err := checkCreds(a.root.srv.state, c) 74 if err != nil { 75 return err 76 } 77 if a.reqNotifier != nil { 78 a.reqNotifier.login(entity.Tag()) 79 } 80 // We have authenticated the user; now choose an appropriate API 81 // to serve to them. 82 newRoot, err := a.apiRootForEntity(entity, c) 83 if err != nil { 84 return err 85 } 86 87 a.root.rpcConn.Serve(newRoot, serverError) 88 return nil 89 } 90 91 func checkCreds(st *state.State, c params.Creds) (taggedAuthenticator, error) { 92 entity0, err := st.FindEntity(c.AuthTag) 93 if err != nil && !errors.IsNotFoundError(err) { 94 return nil, err 95 } 96 // We return the same error when an entity 97 // does not exist as for a bad password, so that 98 // we don't allow unauthenticated users to find information 99 // about existing entities. 100 entity, ok := entity0.(taggedAuthenticator) 101 if !ok { 102 return nil, common.ErrBadCreds 103 } 104 if err != nil || !entity.PasswordValid(c.Password) { 105 return nil, common.ErrBadCreds 106 } 107 return entity, nil 108 } 109 110 // machinePinger wraps a presence.Pinger. 111 type machinePinger struct { 112 *presence.Pinger 113 } 114 115 // Stop implements Pinger.Stop() as Pinger.Kill(), needed at 116 // connection closing time to properly stop the wrapped pinger. 117 func (p *machinePinger) Stop() error { 118 if err := p.Pinger.Stop(); err != nil { 119 return err 120 } 121 return p.Pinger.Kill() 122 } 123 124 func (a *srvAdmin) apiRootForEntity(entity taggedAuthenticator, c params.Creds) (interface{}, error) { 125 // TODO(rog) choose appropriate object to serve. 126 newRoot := newSrvRoot(a.root, entity) 127 128 // If this is a machine agent connecting, we need to check the 129 // nonce matches, otherwise the wrong agent might be trying to 130 // connect. 131 machine, ok := entity.(*state.Machine) 132 if ok { 133 if !machine.CheckProvisioned(c.Nonce) { 134 return nil, state.NotProvisionedError(machine.Id()) 135 } 136 } 137 setAgentAliver, ok := entity.(interface { 138 SetAgentAlive() (*presence.Pinger, error) 139 }) 140 if ok { 141 // A machine or unit agent has connected, so start a pinger to 142 // announce it's now alive, and set up the API pinger 143 // so that the connection will be terminated if a sufficient 144 // interval passes between pings. 145 pinger, err := setAgentAliver.SetAgentAlive() 146 if err != nil { 147 return nil, err 148 } 149 newRoot.resources.Register(&machinePinger{pinger}) 150 action := func() { 151 if err := newRoot.rpcConn.Close(); err != nil { 152 logger.Errorf("error closing the RPC connection: %v", err) 153 } 154 } 155 newRoot.pingTimeout = newPingTimeout(action, maxPingInterval) 156 } 157 return newRoot, nil 158 }