def created_cb(user_details): logging.debug("indx_pg2 create_box, created_cb") rw_user, rw_user_pass, ro_user, ro_user_pass = user_details def got_keys_cb(keys): logging.debug("indx_pg2 create_box, got_keys_cb") # assign ownership now to db_owner rw_pw_encrypted = rsa_encrypt(keys['public'], rw_user_pass) ro_pw_encrypted = rsa_encrypt(keys['public'], ro_user_pass) def indx_db(conn_indx): logging.debug("indx_pg2 create_box, indx_db") d_q = conn_indx.runOperation("INSERT INTO tbl_keychain (user_id, db_name, db_user, db_user_type, db_password_encrypted) VALUES ((SELECT id_user FROM tbl_users WHERE username = %s), %s, %s, %s, %s), ((SELECT id_user FROM tbl_users WHERE username = %s), %s, %s, %s, %s)", [db_owner, db_name, rw_user, 'rw', rw_pw_encrypted, db_owner, db_name, ro_user, 'ro', ro_pw_encrypted]) def inserted(empty): logging.debug("indx_pg2 create_box, inserted, next ACL") acl_q = conn_indx.runOperation("INSERT INTO tbl_acl (database_name, user_id, acl_read, acl_write, acl_owner, acl_control) VALUES (%s, (SELECT id_user FROM tbl_users WHERE username = %s), %s, %s, %s, %s)", [box_name, db_owner, True, True, True, True]) def inserted_acl(empty): logging.debug("indx_pg2 create_box, inserted_acl - create_box finished") return_d.callback(True) acl_q.addCallbacks(inserted_acl, return_d.errback) d_q.addCallbacks(inserted, return_d.errback) # connect to INDX db to add new DB accounts to keychain self.connect_indx_db().addCallbacks(indx_db, return_d.errback) user = IndxUser(self, db_owner) user.get_keys().addCallbacks(got_keys_cb, return_d.errback)
def token_cb(token): username = token.username # wbSession = self.get_session(request) user = IndxUser(self.database, username) force_get = False if force_get in subhandler: force_get = subhandler['force_get'] def acl_cb(acl): logging.debug("BaseHandler _matches_acl_requirements got acl: {0}".format(acl)) permissions = acl['acl'] for key in req_acl: if key not in permissions: # key (e.g. "read", "write", "admin") must be in the user's acl, otherwise Fail return_d.callback(False) return return_d if not permissions[key]: # if the key isn't True, then fail return_d.callback(False) return return_d # no failures, pass. return_d.callback(True) return return_d user.get_acl(self.get_request_box(request, force_get = force_get)).addCallbacks(acl_cb, return_d.errback)
def token_cb(token): username = token.username # wbSession = self.get_session(request) user = IndxUser(self.database, username) force_get = False if force_get in subhandler: force_get = subhandler['force_get'] def acl_cb(acl): logging.debug( "BaseHandler _matches_acl_requirements got acl: {0}". format(acl)) permissions = acl['acl'] for key in req_acl: if key not in permissions: # key (e.g. "read", "write", "admin") must be in the user's acl, otherwise Fail return_d.callback(False) return return_d if not permissions[ key]: # if the key isn't True, then fail return_d.callback(False) return return_d # no failures, pass. return_d.callback(True) return return_d user.get_acl(self.get_request_box( request, force_get=force_get)).addCallbacks(acl_cb, return_d.errback)
def store_cb(store): def setacl_cb(empty): def created_cb(empty): def saved_cb(empty): def servervar_cb(empty): # encpk2 = rsa_encrypt(load_key(local_keys['public']), token.password) encpk2 = make_encpk2(local_keys, token.password) self.return_created(request, {"data": {"public": local_keys['public'], "public-hash": local_keys['public-hash'], "encpk2": encpk2, "serverid": self.webserver.server_id}}) if is_linked: self.database.save_linked_box(token.boxid).addCallbacks(servervar_cb, err_cb) else: servervar_cb(None) self.database.save_encpk2(remote_encpk2_hsh, remote_encpk2, remote_serverid).addCallbacks(saved_cb, err_cb) def new_key_added_cb(empty): self.webserver.keystore.put(local_keys, token.username, token.boxid).addCallbacks(created_cb, err_cb) # store in the local keystore remote_keys = {"public": remote_public, "public-hash": remote_hash, "private": ""} self.webserver.keystore.put(remote_keys, token.username, token.boxid).addCallbacks(new_key_added_cb, err_cb) user = IndxUser(self.database, token.username) user.set_acl(token.boxid, "@indx", {"read": True, "write": True, "control": False, "owner": False}).addCallbacks(setacl_cb, lambda failure: self.return_internal_error(request))
def queried(rows): if len(rows) < 1: result_d.callback(False) return if len(rows) > 1: while len(rows) > 0: row = rows.pop(0) if row[1] == 'rw': # db_user_type is 'rw', then break, we have the best account type break else: row = rows[0] def got_keys_cb(keys): logging.debug("indx_pg2 lookup_best_acct got_keys_cb") # now row is the best account db_user, db_user_type, db_password_encrypted = row db_pass = rsa_decrypt(keys['private'], db_password_encrypted) result_d.callback((db_user, db_pass)) return user = IndxUser(self, box_user) user.get_keys().addCallbacks(got_keys_cb, result_d.errback)
def set_acl(self, request, token): """ Set an ACL for this box. RULES (these are in box.py and in user.py) The logged in user sets an ACL for a different, target user. The logged in user must have a token, and the box of the token is the box that will have the ACL changed/set. If there is already an ACL for the target user, it will be replaced. The logged in user must have "control" permissions on the box. The logged in user can give/take read, write or control permissions. They cannot change "owner" permissions. If the user has owner permissions, it doesn't matter if they dont have "control" permissions, they can change anything. Only the user that created the box has owner permissions. """ if not token: return self.return_forbidden(request) BoxHandler.log(logging.DEBUG, "BoxHandler set_acl", extra = {"request": request, "token": token}) wbSession = self.get_session(request) user = IndxUser(self.database, wbSession.username) # box is set by the token (token.boxid) try: req_acl = json.loads(self.get_arg(request, "acl")) except Exception as e: BoxHandler.log(logging.ERROR, "Exception in box.set_acl decoding JSON in query, 'acl': {0}".format(e), extra = {"request": request, "token": token}) return self.return_bad_request(request, "Specify acl as query string parameter 'acl' as valid JSON") req_username = self.get_arg(request, "target_username") # username of the user of which to change the ACL def err_cb(failure): failure.trap(Exception) BoxHandler.log(logging.ERROR, "BoxHandler set_acl err_cb: {0}".format(failure), extra = {"request": request, "token": token}) return self.return_internal_error(request) user.set_acl(token.boxid, req_username, req_acl).addCallbacks(lambda empty: self.return_ok(request), err_cb)
def do_next_row(empty): logging.debug("indx_pg2 missing_key_check, do_next_row") if len(rows) < 1: return_d.callback(True) return new_username = rows.pop(0)[0] user = IndxUser(self, new_username) user.generate_encryption_keys().addCallbacks(do_next_row, return_d.errback)
def keys_cb(keys): logging.debug( "indx_pg2, transfer_keychain_users, connected_cb, keys_cb") # FIXME we will need to decrypt private key somehow private_key = keys['private'] def keys2_cb(keys2): logging.debug( "indx_pg2, transfer_keychain_users, connected_cb, keys2_cb" ) public2_key = keys2['public'] def existing_cb(rows): logging.debug( "indx_pg2, transfer_keychain_users, connected_cb, existing_cb" ) def process_row(empty): if len(rows) < 1: return_d.callback(True) return row = rows.pop(0) db_user, db_user_type, db_password_encrypted = row if db_user_type not in user_types: process_row(None) return # next loop db_password_clear = rsa_decrypt( private_key, db_password_encrypted) db_password_new_encrypted = rsa_encrypt( public2_key, db_password_clear) ins_q = "INSERT INTO tbl_keychain (user_id, db_name, db_user, db_user_type, db_password_encrypted) VALUES ((SELECT id_user FROM tbl_users WHERE username = %s), %s, %s, %s, %s)" ins_p = [ to_user, db_name, db_user, db_user_type, db_password_new_encrypted ] conn.runOperation(ins_q, ins_p).addCallbacks( process_row, return_d.errback) process_row(None) conn.runQuery( "SELECT db_user, db_user_type, db_password_encrypted FROM tbl_keychain JOIN tbl_users ON (tbl_users.id_user = tbl_keychain.user_id) WHERE tbl_users.username = %s AND db_name = %s", [from_user, db_name]).addCallbacks( existing_cb, return_d.errback) to_user_obj = IndxUser(self, to_user) to_user_obj.get_keys().addCallbacks(keys2_cb, return_d.errback)
def init_user(self, new_user_metadata): """ Check there is a user in the database, and initialise one if not. If there are pending permission requests, initialise them too. Callsback with an object with user details. """ logging.debug("IndxOpenID, init_user") return_d = Deferred() user = IndxUser(self.db, self.uri) def info_cb(user_metadata): logging.debug( "IndxOpenID, init_user, info_cb user_metadata: {0}".format( user_metadata)) if user_metadata is None: # user does not exist: create a new user in the table now, with empty password hash def connected_cb(conn): user_metadata_json = json.dumps(new_user_metadata) insert_q = "INSERT INTO tbl_users (username, username_type, password_hash, password_encrypted, user_metadata_json) VALUES (%s, %s, %s, %s, %s)" insert_p = [self.uri, "openid", "", "", user_metadata_json] def inserted_d(empty): logging.debug( "IndxOpenID, init_user, connected_d, query_d, inserted_d" ) def generated_cb(empty): logging.debug( "IndxOpenID, init_user, connected_d, query_d, inserted_d, generated_cb" ) user.get_user_info(decode_json=False).addCallbacks( return_d.callback, return_d.errback) user.generate_encryption_keys().addCallbacks( generated_cb, return_d.errback) conn.runOperation(insert_q, insert_p).addCallbacks( inserted_d, return_d.errback) self.db.connect_indx_db().addCallbacks(connected_cb, return_d.errback) else: # user already exists, no init required here return_d.callback(user_metadata) user.get_user_info(decode_json=False).addCallbacks( info_cb, return_d.errback) return return_d
def connected_cb(conn): logging.debug("indx_pg2 transfer_keychain_users, connected_cb") def keys_cb(keys): logging.debug("indx_pg2, transfer_keychain_users, connected_cb, keys_cb") # FIXME we will need to decrypt private key somehow private_key = keys['private'] def keys2_cb(keys2): logging.debug("indx_pg2, transfer_keychain_users, connected_cb, keys2_cb") public2_key = keys2['public'] def existing_cb(rows): logging.debug("indx_pg2, transfer_keychain_users, connected_cb, existing_cb") def process_row(empty): if len(rows) < 1: return_d.callback(True) return row = rows.pop(0) db_user, db_user_type, db_password_encrypted = row if db_user_type not in user_types: process_row(None) return # next loop db_password_clear = rsa_decrypt(private_key, db_password_encrypted) db_password_new_encrypted = rsa_encrypt(public2_key, db_password_clear) ins_q = "INSERT INTO tbl_keychain (user_id, db_name, db_user, db_user_type, db_password_encrypted) VALUES ((SELECT id_user FROM tbl_users WHERE username = %s), %s, %s, %s, %s)" ins_p = [to_user, db_name, db_user, db_user_type, db_password_new_encrypted] conn.runOperation(ins_q, ins_p).addCallbacks(process_row, return_d.errback) process_row(None) conn.runQuery("SELECT db_user, db_user_type, db_password_encrypted FROM tbl_keychain JOIN tbl_users ON (tbl_users.id_user = tbl_keychain.user_id) WHERE tbl_users.username = %s AND db_name = %s", [from_user, db_name]).addCallbacks(existing_cb, return_d.errback) to_user_obj = IndxUser(self, to_user) to_user_obj.get_keys().addCallbacks(keys2_cb, return_d.errback) from_user_obj = IndxUser(self, from_user) from_user_obj.get_keys().addCallbacks(keys_cb, return_d.errback)
def get_acls(self, request, token): """ Get all of the ACLs for this box. You must have 'control' permission to be able to do this. """ if not token: return self.return_forbidden(request) BoxHandler.log(logging.DEBUG, "BoxHandler get_acls", extra = {"request": request, "token": token}) user = IndxUser(self.database, token.username) def err_cb(failure): failure.trap(Exception) BoxHandler.log(logging.ERROR, "BoxHandler get_acls err_cb: {0}".format(failure), extra = {"request": request, "token": token}) return self.return_internal_error(request) user.get_acls(token.boxid).addCallbacks(lambda results: self.return_ok(request, {"data": results}), err_cb)
def set_acl(self, request, token): """ Set an ACL for this box. RULES (these are in box.py and in user.py) The logged in user sets an ACL for a different, target user. The logged in user must have a token, and the box of the token is the box that will have the ACL changed/set. If there is already an ACL for the target user, it will be replaced. The logged in user must have "control" permissions on the box. The logged in user can give/take read, write or control permissions. They cannot change "owner" permissions. If the user has owner permissions, it doesn't matter if they dont have "control" permissions, they can change anything. Only the user that created the box has owner permissions. """ if not token: return self.return_forbidden(request) BoxHandler.log(logging.DEBUG, "BoxHandler set_acl", extra = {"request": request, "token": token}) user = IndxUser(self.database, token.username) # box is set by the token (token.boxid) try: req_acl = json.loads(self.get_arg(request, "acl")) except Exception as e: BoxHandler.log(logging.ERROR, "Exception in box.set_acl decoding JSON in query, 'acl': {0}".format(e), extra = {"request": request, "token": token}) return self.return_bad_request(request, "Specify acl as query string parameter 'acl' as valid JSON") try: req_username = self.get_arg(request, "target_username") # username of the user of which to change the ACL if req_username is None: raise Exception("") except Exception as e: try: req_public = self.get_arg(request, "unauth_user") if req_public: req_username = UNAUTH_USERNAME # use the magic reserved username in the DB for the unauth user permissions else: return self.return_bad_request(request, "If 'target_username' is not specified, then 'unauth_user' must be set true.") except Exception as e: return self.return_bad_request(request, "If 'target_username' is not specified, then 'unauth_user' must be set true.") def err_cb(failure): failure.trap(Exception) BoxHandler.log(logging.ERROR, "BoxHandler set_acl err_cb: {0}".format(failure), extra = {"request": request, "token": token}) return self.return_internal_error(request) user.set_acl(token.boxid, req_username, req_acl).addCallbacks(lambda empty: self.return_ok(request), err_cb)
def auth_whoami(self, request, token): wbSession = self.get_session(request) logging.debug('auth whoami ' + repr(wbSession)) user = IndxUser(self.database, wbSession.username) def info_cb(user_info): if user_info is None: user_info = {} # add our username and is_authenticated information if 'username' not in user_info: user_info['username'] = wbSession and wbSession.username or 'nobody' user_info['is_authenticated'] = wbSession and wbSession.is_authenticated self.return_ok(request, user_info) # don't decode the user_metadata string, leave as a json string user.get_user_info(decode_json = False).addCallbacks(info_cb, lambda failure: self.return_internal_error(request))
def init_user(self, new_user_metadata): """ Check there is a user in the database, and initialise one if not. If there are pending permission requests, initialise them too. Callsback with an object with user details. """ logging.debug("IndxOpenID, init_user") return_d = Deferred() user = IndxUser(self.db, self.uri) def info_cb(user_metadata): logging.debug("IndxOpenID, init_user, info_cb user_metadata: {0}".format(user_metadata)) if user_metadata is None: # user does not exist: create a new user in the table now, with empty password hash def connected_cb(conn): user_metadata_json = json.dumps(new_user_metadata) insert_q = "INSERT INTO tbl_users (username, username_type, password_hash, password_encrypted, user_metadata_json) VALUES (%s, %s, %s, %s, %s)" insert_p = [self.uri, "openid", "", "", user_metadata_json] def inserted_d(empty): logging.debug("IndxOpenID, init_user, connected_d, query_d, inserted_d") def generated_cb(empty): logging.debug("IndxOpenID, init_user, connected_d, query_d, inserted_d, generated_cb") user.get_user_info(decode_json = False).addCallbacks(return_d.callback, return_d.errback) user.generate_encryption_keys().addCallbacks(generated_cb, return_d.errback) conn.runOperation(insert_q, insert_p).addCallbacks(inserted_d, return_d.errback) self.db.connect_indx_db().addCallbacks(connected_cb, return_d.errback) else: # user already exists, no init required here return_d.callback(user_metadata) user.get_user_info(decode_json = False).addCallbacks(info_cb, return_d.errback) return return_d
def link_remote_box(self, request, token): """ Link a remote box with this box. """ if not token: return self.return_forbidden(request) BoxHandler.log(logging.DEBUG, "BoxHandler link_remote_box", extra = {"request": request, "token": token}) def err_cb(failure): failure.trap(Exception) BoxHandler.log(logging.ERROR, "BoxHandler link_remote_box err_cb: {0}".format(failure), extra = {"request": request, "token": token}) return self.return_internal_error(request) def setacl_cb(empty): remote_address = self.get_arg(request, "remote_address") remote_box = self.get_arg(request, "remote_box") remote_token = self.get_arg(request, "remote_token") if remote_address is None or remote_box is None or remote_token is None: BoxHandler.log(logging.ERROR, "BoxHandler link_remote_box: remote_address, remote_box or remote_token was missing.", extra = {"request": request, "token": token}) return self.remote_bad_request(request, "remote_address, remote_box or remote_token was missing.") def synced_cb(indxsync): def sync_complete_cb(empty): def linked_cb(remote_public_key): # encrypt the user's password using the remote public key, and store # XXX do we need this still? obsolete since enc_pk2 handled in sync #encrypted_remote_pw = rsa_encrypt(load_key(remote_public_key), token.password) self.return_created(request) indxsync.link_remote_box(token.username, token.password, remote_address, remote_box, remote_token, self.webserver.server_id).addCallbacks(linked_cb, err_cb) indxsync.sync_boxes().addCallbacks(sync_complete_cb, err_cb) self.webserver.sync_box(token.boxid).addCallbacks(synced_cb, err_cb) # give read-write access to the @indx user so that the webserver can connect with the user being present user = IndxUser(self.database, token.username) user.set_acl(token.boxid, "@indx", {"read": True, "write": True, "control": False, "owner": False}).addCallbacks(setacl_cb, lambda failure: self.return_internal_error(request))
def login_openid(self, request, token): """ Verify an OpenID identity. """ wbSession = self.get_session(request) identity = self.get_arg(request, "identity") if identity is None: logging.error("login_openid error, identity is None, returning bad request.") return self.return_bad_request(request, "You must specify an 'identity' in the GET/POST query parameters.") redirect = self.get_arg(request, "redirect") wbSession.set_openid_redirect(redirect) if redirect is None: logging.error("login_openid error, redirect is None, returning bad request.") return self.return_bad_request(request, "You must specify a 'redirect' in the GET/POST query parameters.") def post_user_info(request_user_metadata): logging.debug("login_openid post_user_info, request_user_metadata: {0}".format(request_user_metadata)) oid_consumer = consumer.Consumer(self.get_openid_session(request), self.store) try: oid_req = oid_consumer.begin(identity) if request_user_metadata: # SReg speaks this protocol: http://openid.net/specs/openid-simple-registration-extension-1_1-01.html # and tries to request additional metadata about this OpenID identity sreg_req = sreg.SRegRequest(required=['fullname','nickname','email'], optional=[]) oid_req.addExtension(sreg_req) # AX speaks this protocol: http://openid.net/specs/openid-attribute-exchange-1_0.html # and tries to get more attributes (by URI), we request some of the more common ones ax_req = ax.FetchRequest() for uri in self.AX_URIS: ax_req.add(ax.AttrInfo(uri, required = True)) oid_req.addExtension(ax_req) except consumer.DiscoveryFailure as exc: logging.error("Error in login_openid: {0}".format(exc)) self._send_openid_redirect(request, {"status": 401, "message": "Unauthorized"}) return else: if oid_req is None: logging.error("Error in login_openid: no OpenID services found for: {0}".format(identity)) self._send_openid_redirect(request, {"status": 401, "message": "Unauthorized"}) return else: trust_root = self.webserver.server_url return_to = appendArgs(trust_root + "/" + self.base_path + "/openid_process", {}) logging.debug("OpenID, had oid_req, trust_root: {0}, return_to: {1}, oid_req: {2}".format(trust_root, return_to, oid_req)) redirect_url = oid_req.redirectURL(trust_root, return_to) request.setHeader("Location", redirect_url) request.setResponseCode(302, "Found") request.finish() return user = IndxUser(self.database, identity) user.get_user_info().addCallbacks(lambda user_info: post_user_info(user_info is None), lambda failure: self.return_internal_error(request)) # if user_info is None, then request_user_metadata = True return
def login_openid(self, request, token): """ Verify an OpenID identity. """ wbSession = self.get_session(request) identity = self.get_arg(request, "identity") if identity is None: logging.error( "login_openid error, identity is None, returning bad request.") return self.return_bad_request( request, "You must specify an 'identity' in the GET/POST query parameters." ) redirect = self.get_arg(request, "redirect") wbSession.set_openid_redirect(redirect) if redirect is None: logging.error( "login_openid error, redirect is None, returning bad request.") return self.return_bad_request( request, "You must specify a 'redirect' in the GET/POST query parameters." ) def post_user_info(request_user_metadata): logging.debug( "login_openid post_user_info, request_user_metadata: {0}". format(request_user_metadata)) oid_consumer = consumer.Consumer(self.get_openid_session(request), self.store) try: oid_req = oid_consumer.begin(identity) if request_user_metadata: # SReg speaks this protocol: http://openid.net/specs/openid-simple-registration-extension-1_1-01.html # and tries to request additional metadata about this OpenID identity sreg_req = sreg.SRegRequest( required=['fullname', 'nickname', 'email'], optional=[]) oid_req.addExtension(sreg_req) # AX speaks this protocol: http://openid.net/specs/openid-attribute-exchange-1_0.html # and tries to get more attributes (by URI), we request some of the more common ones ax_req = ax.FetchRequest() for uri in self.AX_URIS: ax_req.add(ax.AttrInfo(uri, required=True)) oid_req.addExtension(ax_req) except consumer.DiscoveryFailure as exc: logging.error("Error in login_openid: {0}".format(exc)) self._send_openid_redirect(request, { "status": 401, "message": "Unauthorized" }) return else: if oid_req is None: logging.error( "Error in login_openid: no OpenID services found for: {0}" .format(identity)) self._send_openid_redirect(request, { "status": 401, "message": "Unauthorized" }) return else: trust_root = self.webserver.server_url return_to = appendArgs( trust_root + "/" + self.base_path + "/openid_process", {}) logging.debug( "OpenID, had oid_req, trust_root: {0}, return_to: {1}, oid_req: {2}" .format(trust_root, return_to, oid_req)) redirect_url = oid_req.redirectURL(trust_root, return_to) request.setHeader("Location", redirect_url) request.setResponseCode(302, "Found") request.finish() return user = IndxUser(self.database, identity) user.get_user_info().addCallbacks( lambda user_info: post_user_info(user_info is None), lambda failure: self.return_internal_error(request)) # if user_info is None, then request_user_metadata = True return
def do_acl(empty): logging.debug("indx_pg2 create_root_box do_acl") user = IndxUser(self, username) user.set_acl(box_name, "@indx", {"read": True, "write": False, "control": False, "owner": False}).addCallbacks(result_d.callback, result_d.errback)
def added_cb(empty): user = IndxUser(self, new_username) user.generate_encryption_keys().addCallbacks(lambda *x: return_d.callback(None), return_d.errback)
def added_cb(empty): user = IndxUser(self, new_username) user.generate_encryption_keys().addCallbacks( lambda *x: return_d.callback(None), return_d.errback)