def onSessionOpen(self): """ Entry point when admin UI is connected. """ self.dbpool = self.factory.dbpool self.registerForRpc(self, URI_API, [ AdminWebSocketProtocol.isActivated, AdminWebSocketProtocol.createActivationRequest, AdminWebSocketProtocol.getPasswordSet, AdminWebSocketProtocol.setPassword ]) self.serviceConfig = ServiceConfig(self) ## override global client auth options self.clientAuthTimeout = 120 self.clientAuthAllowAnonymous = False self.authUser = None ## call base class method WampCraServerProtocol.onSessionOpen(self)
def onSessionOpen(self): """ Entry point when admin UI is connected. """ self.dbpool = self.factory.dbpool self.registerForRpc(self, URI_API, [AdminWebSocketProtocol.isActivated, AdminWebSocketProtocol.createActivationRequest, AdminWebSocketProtocol.getPasswordSet, AdminWebSocketProtocol.setPassword]) self.serviceConfig = ServiceConfig(self) ## override global client auth options self.clientAuthTimeout = 120 self.clientAuthAllowAnonymous = False self.authUser = None ## call base class method WampCraServerProtocol.onSessionOpen(self)
class AdminWebSocketProtocol(WampCraServerProtocol): USER_PASSWORD_PATTERN = """^[a-zA-Z0-9_\-!$%&/=()"?+*#,;.:\[\]<>|~{}']*$""" USER_PASSWORD_MIN_LENGTH = 6 USER_PASSWORD_MAX_LENGTH = 20 def onSessionOpen(self): """ Entry point when admin UI is connected. """ self.dbpool = self.factory.dbpool self.registerForRpc(self, URI_API, [AdminWebSocketProtocol.isActivated, AdminWebSocketProtocol.createActivationRequest, AdminWebSocketProtocol.getPasswordSet, AdminWebSocketProtocol.setPassword]) self.serviceConfig = ServiceConfig(self) ## override global client auth options self.clientAuthTimeout = 120 self.clientAuthAllowAnonymous = False self.authUser = None ## call base class method WampCraServerProtocol.onSessionOpen(self) def _getPasswordSet(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["admin-password"]) res = txn.fetchone() if res: password = json_loads(res[0]) return password is not None else: raise Exception(URI_ERROR + "internal-error", "admin-password key not found.") @exportRpc("get-password-set") def getPasswordSet(self): return self.dbpool.runInteraction(self._getPasswordSet) def _getEulaAccepted(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) return eula_accepted else: raise Exception(URI_ERROR + "internal-error", "eula-accepted key not found.") @exportRpc("get-eula-accepted") def getEulaAccepted(self): return self.dbpool.runInteraction(self._getEulaAccepted) def _acceptEula(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) if eula_accepted: raise Exception(URI_ERROR + "illegal-invocation", "EULA already accepted.") else: now = utcnow() txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(now), "eula-accepted"]) self.factory.services["config"].recache(txn) return now else: raise Exception(URI_ERROR + "internal-error", "EULA key not found.") @exportRpc("accept-eula") def acceptEula(self): return self.dbpool.runInteraction(self._acceptEula) def _isActivated(self, txn): if True: return {'license-id': '', 'type': 'BETA', 'connected-cap': 0, 'tls-enabled': True, 'valid-from': '1970-01-01T00:00:00:00Z', 'valid-to': '2020-01-01T00:00:00:00Z'} else: now = utcnow() txn.execute("SELECT license_id, license_type, connection_cap, tls_enabled, valid_from, valid_to FROM license WHERE enabled = 1 AND valid_from <= ? AND valid_to > ?", [now, now]) res = txn.fetchone() if res: return {'license-id': res[0], 'type': res[1], 'connected-cap': res[2], 'tls-enabled': True if res[3] != 0 else False, 'valid-from': res[4], 'valid-to': res[5]} else: return None @exportRpc("is-activated") def isActivated(self): #return True return self.dbpool.runInteraction(self._isActivated) def _createActivationRequest(self, txn, origin, licenseType, extra): LICENSE_TYPES = ['BETA'] if licenseType not in LICENSE_TYPES: raise Exception(URI_ERROR + "illegal-argument", "Unknown license type '%s'" % str(licenseType), LICENSE_TYPES) ## construct license activation request ## hostid = self.factory.services['platform'].getHostId() instanceid = self.serviceConfig._getSingleConfig(txn, "instance-id") msg = {'type': licenseType, 'host-id': hostid, 'instance-id': instanceid} dbcreated = self.serviceConfig._getSingleConfig(txn, "database-created") platform = self.factory.services['platform'].getPlatformInfo() network = self.factory.services['platform'].getNetworkConfig() msg['info'] = {'request-time': utcnow(), 'database-created': dbcreated, 'platform': platform, 'network': network} if extra is not None: msg['extra'] = extra log.msg("created license activation request: %s" % msg) rmsg = json_dumps(msg) ## load instance key pair ## pubkey = str(self.serviceConfig._getSingleConfig(txn, "instance-pub-key")) privkey = str(self.serviceConfig._getSingleConfig(txn, "instance-priv-key")) ## encrypt activation request for Tavendo public key ## and sign encrypted message using instance private key ## (emsg, skey, dig, sig) = encrypt_and_sign(rmsg, privkey, Database.WEBMQ_LICENSE_CA_PUBKEY) payload = "%s,%s,%s,%s,%s,%s" % (emsg, skey, dig, sig, urllib.quote_plus(pubkey), urllib.quote_plus(origin + "/doactivate")) #print payload return {'request': msg, 'url': self.factory.services['master'].licenseserver, 'payload': payload} @exportRpc("create-activation-request") def createActivationRequest(self, origin, licenseType, extra = None): """ Create activation request for appliance. :param origin: Must be filled with "window.location.origin". This will be used to direct the license activation POST back to this instance. :type origin: str :param licenseType: Type of license: currently, only "BETA" is allowed. :type licenseType: str :param extra: User provided extra information like name or email. :type extra: dict Example: session.call("api:create-activation-request", window.location.origin, "BETA", {name: "Foobar Corp.", email: "*****@*****.**"}).then(ab.log, ab.log); """ return self.dbpool.runInteraction(self._createActivationRequest, origin, licenseType, extra) @exportRpc("get-license-options") def getLicenseOptions(self): return self.factory.services['database'].getLicenseOptions() @exportRpc("get-installed-options") def getInstalledOptions(self): return self.factory.services['database'].getInstalledOptions() @exportRpc("login-request") def loginRequest(self, username): """ Challenge-response authentication request. """ if type(username) not in [str, unicode]: raise Exception(URI_ERROR + "illegal-argument", "Expected type str/unicode for agument username, but got %s" % str(type(username))) username = username.encode("utf-8") if username != "admin": raise Exception(URI_ERROR + "invalid-user", "User %s not known" % str(username)) self.authChallenge = ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for i in xrange(16)]) self.authChallengeUser = username return self.authChallenge def _login(self, txn, authResponse, stayLoggedIn): if self.authChallenge is None: raise Exception(URI_ERROR + "login-without-previous-request", "Login attempt without previous login request.") txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) h = hmac.new(pw, self.authChallenge, hashlib.sha256) v = binascii.b2a_base64(h.digest()).strip() if v == str(authResponse): self.authUser = self.authChallengeUser if stayLoggedIn: cookie = ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for i in xrange(64)]) now = utcnow() txn.execute("INSERT INTO cookie (created, username, value) VALUES (?, ?, ?)", [now, "admin", cookie]) self.authCookie = cookie res = cookie else: res = None self.onLogin() return res else: raise Exception(URI_ERROR + "login-failed", "Login failed.") else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.") @exportRpc("login") def login(self, authResponse, stayLoggedIn = False): """ Login the user via a response to a previous authentication challenge. """ if self.authUser is not None: raise Exception(URI_ERROR + "already-authenticated", "Connection is already authenticated") return self.dbpool.runInteraction(self._login, authResponse, stayLoggedIn) def _cookieLogin(self, txn, cookie): if type(cookie) not in [str, unicode]: raise Exception(URI_ERROR + "illegal-argument", "Expected type str/unicode for argument cookie, but got %s" % str(type(cookie))) txn.execute("SELECT created, username FROM cookie WHERE value = ?", [cookie]) res = txn.fetchone() if res is not None: created = parseutc(res[0]) now = datetime.datetime.utcnow() lifetime = (now - created).total_seconds() expiration = self.factory.services["config"].get("auth-cookie-lifetime", 600) if expiration > 0 and lifetime > expiration: txn.execute("DELETE FROM cookie WHERE value = ?", [cookie]) raise Exception(URI_ERROR + "expired-cookie", "Authentication cookie expired.") self.authUser = str(res[1]) self.authCookie = cookie self.onLogin() else: raise Exception(URI_ERROR + "bad-cookie", "Invalid authentication cookie.") @exportRpc("cookie-login") def cookieLogin(self, cookie): """ Login the user via a cookie. """ if self.authUser is not None: raise Exception(URI_ERROR + "already-authenticated", "Connection is already authenticated") return self.dbpool.runInteraction(self._cookieLogin, cookie) def getAuthPermissions(self, authKey, authExtra): ## return permissions which will be granted for the auth key ## when the authentication succeeds return {'permissions': {}} def _getAuthSecret(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) return pw else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.") def getAuthSecret(self, authKey): ## return the auth secret for the given auth key or None when the auth key ## does not exist if authKey != "admin": return None else: return self.dbpool.runInteraction(self._getAuthSecret) def onAuthenticated(self, authKey, perms): """ Called when user was logged in. Register the full set of RPCs and PubSub topics. """ self.authUser = authKey licenseOpts = self.factory.services["database"].getLicenseOptions() #self.serviceConfig = ServiceConfig(self) self.registerForRpc(self.serviceConfig, URI_API) self.appCreds = AppCreds(self) self.registerForRpc(self.appCreds, URI_API) if licenseOpts["hana"]: self.hanaConnects = HanaConnects(self) self.registerForRpc(self.hanaConnects, URI_API) self.hanaPushRules = HanaPushRules(self) self.registerForRpc(self.hanaPushRules, URI_API) self.hanaRemotes = HanaRemotes(self) self.registerForRpc(self.hanaRemotes, URI_API) if licenseOpts["postgresql"]: self.pgConnects = PgConnects(self) self.registerForRpc(self.pgConnects, URI_API) self.pgPushRules = PgPushRules(self) self.registerForRpc(self.pgPushRules, URI_API) self.pgRemotes = PgRemotes(self) self.registerForRpc(self.pgRemotes, URI_API) if licenseOpts["oracle"]: self.oraConnects = OraConnects(self) self.registerForRpc(self.oraConnects, URI_API) self.oraPushRules = OraPushRules(self) self.registerForRpc(self.oraPushRules, URI_API) self.oraRemotes = OraRemotes(self) self.registerForRpc(self.oraRemotes, URI_API) self.postRules = PostRules(self) self.registerForRpc(self.postRules, URI_API) self.ftpUsers = FtpUsers(self) self.registerForRpc(self.ftpUsers, URI_API) self.serviceKeys = ServiceKeys(self) self.registerForRpc(self.serviceKeys, URI_API) self.clientPerms = ClientPerms(self) self.registerForRpc(self.clientPerms, URI_API) self.extDirectRemotes = ExtDirectRemotes(self) self.registerForRpc(self.extDirectRemotes, URI_API) self.restRemotes = RestRemotes(self) self.registerForRpc(self.restRemotes, URI_API) self.serviceStatus = ServiceStatus(self) self.registerForRpc(self.serviceStatus, URI_API) self.serviceControl = ServiceControl(self) self.registerForRpc(self.serviceControl, URI_API) self.registerForRpc(self, URI_API) ## register prefix URI_EVENT for topics self.registerForPubSub(URI_EVENT, True) ## register prefix URI_WIRETAP_EVENT for topics self.registerForPubSub(URI_WIRETAP_EVENT, True) def onLogout(self): """ Called when user is logged out. Automatically close WebSocket connection. """ self.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL, "logged out") def _logout(self, txn): if self.authCookie is not None: txn.execute("DELETE FROM cookie WHERE value = ?", [self.authCookie]) self.dispatch(URI_EVENT + "on-logout", self.authCookie, eligible = [self]) self.authUser = None self.authCookie = None self.onLogout() @exportRpc("logout") def logout(self): """ Logout the user. If the user was authenticated via a cookie, the cookie is expired/deleted. """ self.raiseIfNotAuthenticated() return self.dbpool.runInteraction(self._logout) def _setPassword(self, txn, password1, password2): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = json_loads(res[0]) if pw is not None: raise Exception((URI_ERROR + "invalid-invocation", "Initial password already set.")) else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.") attrs = {"password1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "password2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN)} errcnt, errs = self.checkDictArg("user password", {"password1": password1, "password2": password2}, attrs) if password1 != password2: errcnt += 1 if not errs.has_key('password1') or errs.has_key('password2'): p = 'password1' else: p = 'password2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "Passwords do not match")) if errcnt: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(password1), "admin-password"]) @exportRpc("set-password") def setPassword(self, password1, password2): """ Set initial password. """ return self.dbpool.runInteraction(self._setPassword, password1, password2) def _changePassword(self, txn, oldpassword, newpassword1, newpassword2): attrs = {"oldpassword": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN)} errcnt, errs = self.checkDictArg("user password", {"oldpassword": oldpassword, "newpassword1": newpassword1, "newpassword2": newpassword2}, attrs) if newpassword1 != newpassword2: errcnt += 1 if not errs.has_key('newpassword1') or errs.has_key('newpassword2'): p = 'newpassword1' else: p = 'newpassword2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "New password values do not match")) if errcnt: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) if pw == oldpassword: if newpassword1 != oldpassword: txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(newpassword1), "admin-password"]) else: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 2, {'newpassword1': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")], 'newpassword2': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")]}) else: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 1, {'oldpassword': [(self.shrink(URI_ERROR + "invalid-attribute-value"), "Old password is invalid")]}) else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.") @exportRpc("change-password") def changePassword(self, oldpassword, newpassword1, newpassword2): """ Change the password of the currently logged in user. """ self.raiseIfNotAuthenticated() return self.dbpool.runInteraction(self._changePassword, oldpassword, newpassword1, newpassword2) def raiseIfNotAuthenticated(self): if self.authUser is None: raise Exception(URI_ERROR + "not-authenticated", "Connection is not authenticated") ################################################################################################## ## FIXME: REFACTOR THE FOLLOWING def uriToId(self, uri): """ Create object ID within database (which is a UUID) from a object URI. """ return uri[uri.rfind("/") + 1:] def validateUri(self, uri, allowEmptyNetworkLocation = True): ## valid URI: absolute URI from http(s) scheme, no query component ## errs = [] normalizedUri = None try: p = urlparse.urlparse(uri) if p.scheme == "": errs.append((self.shrink(URI_ERROR + "missing-uri-scheme"), "URI '%s' does not contain a scheme." % uri)) else: if p.scheme not in ['http', 'https']: errs.append((self.shrink(URI_ERROR + "invalid-uri-scheme"), "URI '%s' scheme '%s' is invalid (only 'http' or 'https' allowed." % (uri, p.scheme))) if p.netloc == "" and not allowEmptyNetworkLocation: errs.append((self.shrink(URI_ERROR + "missing-uri-network-location"), "URI '%s' does not contain a network location." % uri)) if p.query != "": errs.append((self.shrink(URI_ERROR + "uri-contains-query-component"), "URI '%s' contains a query component '%s'." % (uri, p.query))) normalizedUri = urlparse.urlunparse(p) except Exception, e: errs.append((self.shrink(URI_ERROR + "invalid-uri"), "Invalid URI '%s' - could not parse URI (%s)" % (uri, str(e)))) return (normalizedUri, errs)
class AdminWebSocketProtocol(WampCraServerProtocol): USER_PASSWORD_PATTERN = """^[a-zA-Z0-9_\-!$%&/=()"?+*#,;.:\[\]<>|~{}']*$""" USER_PASSWORD_MIN_LENGTH = 6 USER_PASSWORD_MAX_LENGTH = 20 def onSessionOpen(self): """ Entry point when admin UI is connected. """ self.dbpool = self.factory.dbpool self.registerForRpc(self, URI_API, [ AdminWebSocketProtocol.isActivated, AdminWebSocketProtocol.createActivationRequest, AdminWebSocketProtocol.getPasswordSet, AdminWebSocketProtocol.setPassword ]) self.serviceConfig = ServiceConfig(self) ## override global client auth options self.clientAuthTimeout = 120 self.clientAuthAllowAnonymous = False self.authUser = None ## call base class method WampCraServerProtocol.onSessionOpen(self) def _getPasswordSet(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["admin-password"]) res = txn.fetchone() if res: password = json_loads(res[0]) return password is not None else: raise Exception(URI_ERROR + "internal-error", "admin-password key not found.") @exportRpc("get-password-set") def getPasswordSet(self): return self.dbpool.runInteraction(self._getPasswordSet) def _getEulaAccepted(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) return eula_accepted else: raise Exception(URI_ERROR + "internal-error", "eula-accepted key not found.") @exportRpc("get-eula-accepted") def getEulaAccepted(self): return self.dbpool.runInteraction(self._getEulaAccepted) def _acceptEula(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) if eula_accepted: raise Exception(URI_ERROR + "illegal-invocation", "EULA already accepted.") else: now = utcnow() txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(now), "eula-accepted"]) self.factory.services["config"].recache(txn) return now else: raise Exception(URI_ERROR + "internal-error", "EULA key not found.") @exportRpc("accept-eula") def acceptEula(self): return self.dbpool.runInteraction(self._acceptEula) def _isActivated(self, txn): if True: return { 'license-id': '', 'type': 'BETA', 'connected-cap': 0, 'tls-enabled': True, 'valid-from': '1970-01-01T00:00:00:00Z', 'valid-to': '2020-01-01T00:00:00:00Z' } else: now = utcnow() txn.execute( "SELECT license_id, license_type, connection_cap, tls_enabled, valid_from, valid_to FROM license WHERE enabled = 1 AND valid_from <= ? AND valid_to > ?", [now, now]) res = txn.fetchone() if res: return { 'license-id': res[0], 'type': res[1], 'connected-cap': res[2], 'tls-enabled': True if res[3] != 0 else False, 'valid-from': res[4], 'valid-to': res[5] } else: return None @exportRpc("is-activated") def isActivated(self): #return True return self.dbpool.runInteraction(self._isActivated) def _createActivationRequest(self, txn, origin, licenseType, extra): LICENSE_TYPES = ['BETA'] if licenseType not in LICENSE_TYPES: raise Exception(URI_ERROR + "illegal-argument", "Unknown license type '%s'" % str(licenseType), LICENSE_TYPES) ## construct license activation request ## hostid = self.factory.services['platform'].getHostId() instanceid = self.serviceConfig._getSingleConfig(txn, "instance-id") msg = { 'type': licenseType, 'host-id': hostid, 'instance-id': instanceid } dbcreated = self.serviceConfig._getSingleConfig( txn, "database-created") platform = self.factory.services['platform'].getPlatformInfo() network = self.factory.services['platform'].getNetworkConfig() msg['info'] = { 'request-time': utcnow(), 'database-created': dbcreated, 'platform': platform, 'network': network } if extra is not None: msg['extra'] = extra log.msg("created license activation request: %s" % msg) rmsg = json_dumps(msg) ## load instance key pair ## pubkey = str( self.serviceConfig._getSingleConfig(txn, "instance-pub-key")) privkey = str( self.serviceConfig._getSingleConfig(txn, "instance-priv-key")) ## encrypt activation request for Tavendo public key ## and sign encrypted message using instance private key ## (emsg, skey, dig, sig) = encrypt_and_sign(rmsg, privkey, Database.WEBMQ_LICENSE_CA_PUBKEY) payload = "%s,%s,%s,%s,%s,%s" % ( emsg, skey, dig, sig, urllib.quote_plus(pubkey), urllib.quote_plus(origin + "/doactivate")) #print payload return { 'request': msg, 'url': self.factory.services['master'].licenseserver, 'payload': payload } @exportRpc("create-activation-request") def createActivationRequest(self, origin, licenseType, extra=None): """ Create activation request for appliance. :param origin: Must be filled with "window.location.origin". This will be used to direct the license activation POST back to this instance. :type origin: str :param licenseType: Type of license: currently, only "BETA" is allowed. :type licenseType: str :param extra: User provided extra information like name or email. :type extra: dict Example: session.call("api:create-activation-request", window.location.origin, "BETA", {name: "Foobar Corp.", email: "*****@*****.**"}).then(ab.log, ab.log); """ return self.dbpool.runInteraction(self._createActivationRequest, origin, licenseType, extra) @exportRpc("get-license-options") def getLicenseOptions(self): return self.factory.services['database'].getLicenseOptions() @exportRpc("get-installed-options") def getInstalledOptions(self): return self.factory.services['database'].getInstalledOptions() @exportRpc("login-request") def loginRequest(self, username): """ Challenge-response authentication request. """ if type(username) not in [str, unicode]: raise Exception( URI_ERROR + "illegal-argument", "Expected type str/unicode for agument username, but got %s" % str(type(username))) username = username.encode("utf-8") if username != "admin": raise Exception(URI_ERROR + "invalid-user", "User %s not known" % str(username)) self.authChallenge = ''.join([ random.choice( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ) for i in xrange(16) ]) self.authChallengeUser = username return self.authChallenge def _login(self, txn, authResponse, stayLoggedIn): if self.authChallenge is None: raise Exception(URI_ERROR + "login-without-previous-request", "Login attempt without previous login request.") txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) h = hmac.new(pw, self.authChallenge, hashlib.sha256) v = binascii.b2a_base64(h.digest()).strip() if v == str(authResponse): self.authUser = self.authChallengeUser if stayLoggedIn: cookie = ''.join([ random.choice( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ) for i in xrange(64) ]) now = utcnow() txn.execute( "INSERT INTO cookie (created, username, value) VALUES (?, ?, ?)", [now, "admin", cookie]) self.authCookie = cookie res = cookie else: res = None self.onLogin() return res else: raise Exception(URI_ERROR + "login-failed", "Login failed.") else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.") @exportRpc("login") def login(self, authResponse, stayLoggedIn=False): """ Login the user via a response to a previous authentication challenge. """ if self.authUser is not None: raise Exception(URI_ERROR + "already-authenticated", "Connection is already authenticated") return self.dbpool.runInteraction(self._login, authResponse, stayLoggedIn) def _cookieLogin(self, txn, cookie): if type(cookie) not in [str, unicode]: raise Exception( URI_ERROR + "illegal-argument", "Expected type str/unicode for argument cookie, but got %s" % str(type(cookie))) txn.execute("SELECT created, username FROM cookie WHERE value = ?", [cookie]) res = txn.fetchone() if res is not None: created = parseutc(res[0]) now = datetime.datetime.utcnow() lifetime = (now - created).total_seconds() expiration = self.factory.services["config"].get( "auth-cookie-lifetime", 600) if expiration > 0 and lifetime > expiration: txn.execute("DELETE FROM cookie WHERE value = ?", [cookie]) raise Exception(URI_ERROR + "expired-cookie", "Authentication cookie expired.") self.authUser = str(res[1]) self.authCookie = cookie self.onLogin() else: raise Exception(URI_ERROR + "bad-cookie", "Invalid authentication cookie.") @exportRpc("cookie-login") def cookieLogin(self, cookie): """ Login the user via a cookie. """ if self.authUser is not None: raise Exception(URI_ERROR + "already-authenticated", "Connection is already authenticated") return self.dbpool.runInteraction(self._cookieLogin, cookie) def getAuthPermissions(self, authKey, authExtra): ## return permissions which will be granted for the auth key ## when the authentication succeeds return {'permissions': {}} def _getAuthSecret(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) return pw else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.") def getAuthSecret(self, authKey): ## return the auth secret for the given auth key or None when the auth key ## does not exist if authKey != "admin": return None else: return self.dbpool.runInteraction(self._getAuthSecret) def onAuthenticated(self, authKey, perms): """ Called when user was logged in. Register the full set of RPCs and PubSub topics. """ self.authUser = authKey licenseOpts = self.factory.services["database"].getLicenseOptions() #self.serviceConfig = ServiceConfig(self) self.registerForRpc(self.serviceConfig, URI_API) self.appCreds = AppCreds(self) self.registerForRpc(self.appCreds, URI_API) if licenseOpts["hana"]: self.hanaConnects = HanaConnects(self) self.registerForRpc(self.hanaConnects, URI_API) self.hanaPushRules = HanaPushRules(self) self.registerForRpc(self.hanaPushRules, URI_API) self.hanaRemotes = HanaRemotes(self) self.registerForRpc(self.hanaRemotes, URI_API) if licenseOpts["postgresql"]: self.pgConnects = PgConnects(self) self.registerForRpc(self.pgConnects, URI_API) self.pgPushRules = PgPushRules(self) self.registerForRpc(self.pgPushRules, URI_API) self.pgRemotes = PgRemotes(self) self.registerForRpc(self.pgRemotes, URI_API) if licenseOpts["oracle"]: self.oraConnects = OraConnects(self) self.registerForRpc(self.oraConnects, URI_API) self.oraPushRules = OraPushRules(self) self.registerForRpc(self.oraPushRules, URI_API) self.oraRemotes = OraRemotes(self) self.registerForRpc(self.oraRemotes, URI_API) self.postRules = PostRules(self) self.registerForRpc(self.postRules, URI_API) self.ftpUsers = FtpUsers(self) self.registerForRpc(self.ftpUsers, URI_API) self.serviceKeys = ServiceKeys(self) self.registerForRpc(self.serviceKeys, URI_API) self.clientPerms = ClientPerms(self) self.registerForRpc(self.clientPerms, URI_API) self.extDirectRemotes = ExtDirectRemotes(self) self.registerForRpc(self.extDirectRemotes, URI_API) self.restRemotes = RestRemotes(self) self.registerForRpc(self.restRemotes, URI_API) self.serviceStatus = ServiceStatus(self) self.registerForRpc(self.serviceStatus, URI_API) self.serviceControl = ServiceControl(self) self.registerForRpc(self.serviceControl, URI_API) self.registerForRpc(self, URI_API) ## register prefix URI_EVENT for topics self.registerForPubSub(URI_EVENT, True) ## register prefix URI_WIRETAP_EVENT for topics self.registerForPubSub(URI_WIRETAP_EVENT, True) def onLogout(self): """ Called when user is logged out. Automatically close WebSocket connection. """ self.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL, "logged out") def _logout(self, txn): if self.authCookie is not None: txn.execute("DELETE FROM cookie WHERE value = ?", [self.authCookie]) self.dispatch(URI_EVENT + "on-logout", self.authCookie, eligible=[self]) self.authUser = None self.authCookie = None self.onLogout() @exportRpc("logout") def logout(self): """ Logout the user. If the user was authenticated via a cookie, the cookie is expired/deleted. """ self.raiseIfNotAuthenticated() return self.dbpool.runInteraction(self._logout) def _setPassword(self, txn, password1, password2): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = json_loads(res[0]) if pw is not None: raise Exception((URI_ERROR + "invalid-invocation", "Initial password already set.")) else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.") attrs = { "password1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "password2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN) } errcnt, errs = self.checkDictArg("user password", { "password1": password1, "password2": password2 }, attrs) if password1 != password2: errcnt += 1 if not errs.has_key('password1') or errs.has_key('password2'): p = 'password1' else: p = 'password2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "Passwords do not match")) if errcnt: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(password1), "admin-password"]) @exportRpc("set-password") def setPassword(self, password1, password2): """ Set initial password. """ return self.dbpool.runInteraction(self._setPassword, password1, password2) def _changePassword(self, txn, oldpassword, newpassword1, newpassword2): attrs = { "oldpassword": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN) } errcnt, errs = self.checkDictArg( "user password", { "oldpassword": oldpassword, "newpassword1": newpassword1, "newpassword2": newpassword2 }, attrs) if newpassword1 != newpassword2: errcnt += 1 if not errs.has_key('newpassword1') or errs.has_key( 'newpassword2'): p = 'newpassword1' else: p = 'newpassword2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "New password values do not match")) if errcnt: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) if pw == oldpassword: if newpassword1 != oldpassword: txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(newpassword1), "admin-password"]) else: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 2, { 'newpassword1': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")], 'newpassword2': [ (self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged") ] }) else: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 1, { 'oldpassword': [(self.shrink(URI_ERROR + "invalid-attribute-value"), "Old password is invalid")] }) else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.") @exportRpc("change-password") def changePassword(self, oldpassword, newpassword1, newpassword2): """ Change the password of the currently logged in user. """ self.raiseIfNotAuthenticated() return self.dbpool.runInteraction(self._changePassword, oldpassword, newpassword1, newpassword2) def raiseIfNotAuthenticated(self): if self.authUser is None: raise Exception(URI_ERROR + "not-authenticated", "Connection is not authenticated") ################################################################################################## ## FIXME: REFACTOR THE FOLLOWING def uriToId(self, uri): """ Create object ID within database (which is a UUID) from a object URI. """ return uri[uri.rfind("/") + 1:] def validateUri(self, uri, allowEmptyNetworkLocation=True): ## valid URI: absolute URI from http(s) scheme, no query component ## errs = [] normalizedUri = None try: p = urlparse.urlparse(uri) if p.scheme == "": errs.append((self.shrink(URI_ERROR + "missing-uri-scheme"), "URI '%s' does not contain a scheme." % uri)) else: if p.scheme not in ['http', 'https']: errs.append((self.shrink( URI_ERROR + "invalid-uri-scheme" ), "URI '%s' scheme '%s' is invalid (only 'http' or 'https' allowed." % (uri, p.scheme))) if p.netloc == "" and not allowEmptyNetworkLocation: errs.append( (self.shrink(URI_ERROR + "missing-uri-network-location"), "URI '%s' does not contain a network location." % uri)) if p.query != "": errs.append( (self.shrink(URI_ERROR + "uri-contains-query-component"), "URI '%s' contains a query component '%s'." % (uri, p.query))) normalizedUri = urlparse.urlunparse(p) except Exception, e: errs.append((self.shrink(URI_ERROR + "invalid-uri"), "Invalid URI '%s' - could not parse URI (%s)" % (uri, str(e)))) return (normalizedUri, errs)