def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException("JSON-RPC does not support positional and keyword arguments at the same time") if not self.__cookieProcessor.has_token(): # get the cookie self.__opener.open(self.__serviceURL).read() if len(kwargs): postdata = dumps({"method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc'}) else: postdata = dumps({"method": self.__serviceName, 'params': args, 'id': 'jsonrpc'}) try: if self.__mode == 'POST': respdata = self.__opener.open(self.__serviceURL, postdata.encode('utf8')).read() else: respdata = self.__opener.open(self.__serviceURL + "?" + quote(postdata.encode('utf8'))).read() except HTTPError as e: error = loads(e.fp.readline()) raise JSONRPCException(error['error']['message']) resp = loads(respdata) if resp['error'] != None: raise JSONRPCException(resp['error']) return resp['result']
def process(self, all_props, key, value): errors = [] # Each item of value has to match the given length-rules for idx, entry in enumerate(value): try: loads(entry) except Exception as e: errors.append(dict(index=idx, detail=N_("value is no valid JSON string: '%(error)s'"), error=str(e))) return True, errors
def commandReceived(self, topic, message): """ Process incoming commands, coming in with session and message information. ================= ========================== Parameter Description ================= ========================== message Received MQTT message ================= ========================== Incoming messages are coming from an :class:`gosa.common.components.mqtt_proxy.MQTTServiceProxy`. The command result is written to the '<domain>.client.<client-uuid>' queue. """ err = None res = None name = None args = None kwargs = None id_ = '' response_topic = "%s/response" % "/".join(topic.split("/")[0:4]) try: req = loads(message) except Exception as e: err = str(e) self.log.error("ServiceRequestNotTranslatable: %s" % err) req = {'id': topic.split("/")[-2]} if err is None: try: id_ = req['id'] name = req['method'] args = req['params'] kwargs = req['kwparams'] except KeyError as e: self.log.error("KeyError: %s" % e) err = str(BadServiceRequest(message)) self.log.debug("received call [%s] for %s: %s(%s,%s)" % (id_, topic, name, args, kwargs)) # Try to execute if err is None: try: res = self.__cr.dispatch(name, *args, **kwargs) except Exception as e: err = str(e) # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error(traceback.format_exception(exc_type, exc_value, exc_traceback)) self.log.debug("returning call [%s]: %s / %s" % (id_, res, err)) response = dumps({"result": res, "id": id_}) # Get rid of it... self.client.send_message(response, topic=response_topic)
def handle_message(self, msg): message_end = msg[-2:] == b'\n\n' for line in msg.strip().splitlines(): if line == b'ping': # skip the ping events continue (field, value) = line.decode().split(":", 1) field = field.strip() if field == "data": self.last_data += value.strip() if field == "id": self.last_id = value.strip() if field == "event": self.last_event = value.strip() assert self.last_event == self.check_event if message_end: data = loads(self.last_data) for key in self.check_data: assert key in data assert data[key] == self.check_data[key] self.last_data = "" print("msg id %s (%s) received" % (self.last_id, data['uuid'])) self.stop()
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn uuid = self.__dn_to_uuid(object_dn) factor_method = self.get_method_from_user(uuid) user_settings = self.__settings[ uuid] if uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": challenge = user_settings.pop('_u2f_challenge_') data = loads(key) device, c, t = complete_authentication(challenge, data, [self.facet]) return {'keyHandle': device['keyHandle'], 'touch': t, 'counter': c} elif factor_method is None: return True return False
def test_getSessionUser(self): self.login() data = dumps({"id": 1, "method": "getSessionUser", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 200 json = loads(response.body) assert json['result'] == "admin"
def parse_event(self, rawEvent): """ Parse the SSE event and call the on_event method with the created event dict :param str rawEvent: the raw string retrieved from the SSE server """ event = Event() for line in rawEvent.strip().splitlines(): parts = line.decode().split(":", 1) if len(parts) == 2: field = parts[0].strip() if field == "data": try: data = loads(parts[1].strip()) except JSONDecodeError as e: # no json just use string data = parts[1].strip() if event.data is None: event.data = data else: event.data = "%s\n%s" % (event.data, data) elif field == "id": event.id = parts[1].strip() elif field == "event": event.name = parts[1].strip() if event.data is not None: self.on_event(event)
def process(self, obj, key, valDict): if type(valDict[key]['value'] is not None): try: valDict[key]['value'] = list(map(lambda x: loads(x), valDict[key]['value'])) except json.decoder.JSONDecodeError as e: self.log.error("invalid JSON value property %s [DN=%s]: %s" % (key, obj.dn if obj is not None else '', valDict[key]['value'])) return key, valDict
def configureUserMenu(self, user, user_menu): """ configure a users application menu """ user_menu = loads(user_menu) pw_ent = pwd.getpwnam(user) uid = pw_ent.pw_uid gid = pw_ent.pw_gid self.__initialized_dirs = [] self.home_dir = os.path.expanduser('~%s' % user) self.username = user self.init_directories(user_menu) scripts = self.init_applications(user_menu) self.init_menu(user_menu) self.chown_dirs(uid, gid) if len(scripts) > 0: # script files need to be chowned to user always for script in scripts: self.__chown(script['path'], uid, gid) os.chmod(script['path'], stat.S_IRUSR | stat.S_IXUSR) for script in scripts: # Write gosaApplicationParameter values in execution environment environment = {} for env_entry in script['environment']: environment.update(env_entry) script_log = os.path.join( self.home_dir, self.local_application_scripts_log, os.path.basename(script['path']) + '.log') # Run command as user using sudo cmd = [ 'sudo', '-n', '-u', user, '"DISPLAY=:0"', '-i', script['path'] ] self.log.debug( "executing {script} as {user}, logging to {log}".format( script=" ".join(cmd), user=pw_ent.pw_name, log=script_log)) try: with open(script_log, 'w+') as logfile: p = subprocess.Popen(cmd, shell=True, env=environment, bufsize=-1, stdout=logfile, stderr=logfile, close_fds=True) returncode = p.wait() if returncode != 0: self.log.error( "{} was terminated by signal {}: check {}".format( script['path'], returncode, script_log)) else: self.log.info("{} returned {}".format( script['path'], returncode)) except OSError as e: self.log.error("%s execution failed: %s" % (script['path'], str(e))) finally: if os.path.exists(script_log): self.__chown(script_log, uid, gid)
def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException("JSON-RPC does not support positional and keyword arguments at the same time") # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send if len(kwargs): postdata = dumps({"method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc'}) else: postdata = dumps({"method": self.__serviceName, 'params': args, 'id': 'jsonrpc'}) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) raise gen.Return(response)
def process(self, topic, message): try: req = loads(message) except ValueError as e: raise ValueError(C.make_error("INVALID_JSON", data=str(e))) try: id_ = req['id'] name = req['method'] args = req['params'] kwargs = req['kwparams'] if 'user' in req: user = req['user'] else: user = topic.split("/")[2] sid = req['session_id'] if 'session_id' in req else None except KeyError as e: self.log.error("KeyError: %s" % e) raise BadServiceRequest(message) self.log.debug("received call [%s, user=%s, session-id=%s] for %s: %s(%s,%s)" % (id_, user, sid, topic, name, args, kwargs)) try: return id_, self.__command_registry.dispatch(user, sid, name, *args, **kwargs) except Exception as e: # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error("".join(traceback.format_exception(exc_type, exc_value, exc_traceback))) raise e
def setPasswordRecoveryAnswers(self, user, object_dn, data): """ Set the password recovery answers for a user """ data = loads(data) # Do we have read permissions for the requested attribute env = Environment.getInstance() topic = "%s.objects.%s.attributes.%s" % (env.domain, "User", "passwordRecoveryHash") aclresolver = PluginRegistry.getInstance("ACLResolver") if not aclresolver.check(user, topic, "w", base=object_dn): self.__log.debug("user '%s' has insufficient permissions to write %s on %s, required is %s:%s" % ( user, "isLocked", object_dn, topic, "w")) raise ACLException(C.make_error('PERMISSION_ACCESS', topic, target=object_dn)) user = ObjectProxy(object_dn) method = user.passwordMethod # Try to detect the responsible password method-class pwd_o = self.get_method_by_method_type(method) if not pwd_o: raise PasswordException(C.make_error("PASSWORD_UNKNOWN_HASH", type=method)) # hash the new answers for idx, answer in data.items(): data[idx] = pwd_o.generate_password_hash(self.clean_string(answer), method) print("%s encrypted with %s as index %s => %s" % (self.clean_string(answer), method, idx, data[idx])) # Set the password and commit the changes user.passwordRecoveryHash = dumps(data) user.commit()
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn uuid = self.__dn_to_uuid(object_dn) factor_method = self.get_method_from_user(uuid) user_settings = self.__settings[uuid] if uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": challenge = user_settings.pop('_u2f_challenge_') data = loads(key) device, c, t = complete_authentication(challenge, data, [self.facet]) return { 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c } elif factor_method is None: return True return False
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn user = ObjectProxy(object_dn) factor_method = self.get_method_from_user(user) user_settings = self.__settings[ user.uuid] if user.uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": devices = [ DeviceRegistration.wrap(device) for device in user_settings.get('_u2f_devices_', []) ] challenge = user_settings.pop('_u2f_challenge_') data = loads(key) c, t = verify_authenticate(devices, challenge, data, [self.facet]) return {'touch': t, 'counter': c} elif factor_method is None: return True return False
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn user = ObjectProxy(object_dn) factor_method = self.get_method_from_user(user) user_settings = self.__settings[user.uuid] if user.uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": devices = [DeviceRegistration.wrap(device) for device in user_settings.get('_u2f_devices_', [])] challenge = user_settings.pop('_u2f_challenge_') data = loads(key) c, t = verify_authenticate(devices, challenge, data, [self.facet]) return { 'touch': t, 'counter': c } elif factor_method is None: return True return False
def process(self, topic, message): try: req = loads(message) except ValueError as e: raise ValueError(C.make_error("INVALID_JSON", data=str(e))) try: id_ = req['id'] name = req['method'] args = req['params'] user = req['user'] if 'user' in req else topic.split("/")[2] sid = req['session_id'] if 'session_id' in req else None except KeyError as e: self.log.error("KeyError: %s" % e) raise BadServiceRequest(message) self.log.debug("received call [%s] for %s: %s(%s)" % (id_, topic, name, args)) try: return id_, self.__command_registry.dispatch( user, sid, name, *args) except Exception as e: # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error("".join( traceback.format_exception(exc_type, exc_value, exc_traceback))) raise e
def __init__(self): self.env = Environment.getInstance() settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") if not os.path.exists(os.path.dirname(settings_file)): os.makedirs(os.path.dirname(settings_file)) if os.path.exists(settings_file): with open(settings_file, 'r') as f: self.__hooks = loads(f.read())
def test_unknown(self): self.login() data = dumps({"id": 1, "method": "unknownmethod", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def getDashboardWidgets(self): plugins = [] for root, dirs, files in os.walk(os.path.join(frontend_path, 'gosa', 'uploads', 'widgets')): for d in dirs: manifest_path = os.path.join(root, d, "Manifest.json") if os.path.exists(manifest_path): with open(manifest_path) as f: plugin_data = loads(f.read()) plugins.append(plugin_data) return plugins
def test_login(self): # successful login with unittest.mock.patch.object(JsonRpcHandler, 'authenticate', return_value='cn=System Administrator,ou=people,dc=example,' 'dc=net') as m: response = self.login() assert response.code == 200 json = loads(response.body) assert json['result']['state'] == AUTH_SUCCESS assert json['error'] is None assert json['id'] == 0 # failed login with unittest.mock.patch.object(JsonRpcHandler, 'authenticate', return_value=False) as m: response = self.login() json = loads(response.body) assert json['result']['state'] == AUTH_LOCKED # reset lock JsonRpcHandler._JsonRpcHandler__dos_manager = {}
def __load_widgets(self, path): widgets = [] for root, dirs, files in os.walk(path): for d in dirs: manifest_path = os.path.join(root, d, "Manifest.json") if os.path.exists(manifest_path): with open(manifest_path) as f: plugin_data = loads(f.read()) widgets.append(plugin_data) return widgets
def getDashboardWidgets(self): plugins = [] #TODO: wrong path, needs to be configurable for root, dirs, files in os.walk(os.path.join(frontend_path, 'gosa', 'uploads', 'widgets')): for d in dirs: manifest_path = os.path.join(root, d, "Manifest.json") if os.path.exists(manifest_path): with open(manifest_path) as f: plugin_data = loads(f.read()) plugins.append(plugin_data) return plugins
def configureUserMenu(self, user, user_menu): """ configure a users application menu """ user_menu = loads(user_menu) pw_ent = pwd.getpwnam(user) uid = pw_ent.pw_uid gid = pw_ent.pw_gid self.__initialized_dirs = [] self.home_dir = os.path.expanduser('~%s' % user) self.username = user self.init_directories(user_menu) scripts = self.init_applications(user_menu) self.init_menu(user_menu) self.chown_dirs(uid, gid) if len(scripts) > 0: # script files need to be chowned to user always for script in scripts: self.__chown(script['path'], uid, gid) os.chmod(script['path'], stat.S_IRUSR | stat.S_IXUSR) for script in scripts: # Write gosaApplicationParameter values in execution environment environment = {} for env_entry in script['environment']: environment.update(env_entry) script_log = os.path.join(self.home_dir, self.local_application_scripts_log, os.path.basename(script['path']) + '.log') # Run command as user using sudo cmd = ['sudo', '-n', '-u', user, 'DISPLAY={DISPLAY}'.format(DISPLAY=self.getDisplay()), '-i', script['path']] self.log.debug("executing {script} as {user}, logging to {log}".format(script=" ".join(cmd), user=pw_ent.pw_name, log=script_log)) try: with open(script_log, 'w+') as logfile: p = subprocess.Popen( cmd, shell=False, env=environment, bufsize=-1, stdout=logfile, stderr=logfile, close_fds=True ) returncode = p.wait() if returncode != 0: self.log.error("{} was terminated by signal {}: check {}".format(script['path'], returncode, script_log)) else: self.log.info("{} returned {}".format(script['path'], returncode)) except OSError as e: self.log.error("%s execution failed: %s" % (script['path'], str(e))) finally: if os.path.exists(script_log): self.__chown(script_log, uid, gid)
def test_exception(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": { 'test': 'test' } }) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def test_getSessionUser(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 200 json = loads(response.body) assert json['result'] == "admin"
def test_exception(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": {'test': 'test'} }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 500 assert json['error']['name'] == "JSONRPCError"
def test_login(self): # successful login with unittest.mock.patch.object( JsonRpcHandler, 'authenticate', return_value='cn=System Administrator,ou=people,dc=example,' 'dc=net') as m: response = self.login() assert response.code == 200 json = loads(response.body) assert json['result']['state'] == AUTH_SUCCESS assert json['error'] is None assert json['id'] == 0 # failed login with unittest.mock.patch.object(JsonRpcHandler, 'authenticate', return_value=False) as m: response = self.login() json = loads(response.body) assert json['result']['state'] == AUTH_LOCKED # reset lock JsonRpcHandler._JsonRpcHandler__dos_manager = {}
def test_unknown(self): self.login() data = dumps({ "id": 1, "method": "unknownmethod", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def __on_message(self, client, userdata, message): payload = loads(message.payload) if self.__sender_id is not None and payload['sender_id'] == self.__sender_id: # skip own messages return subs = self.get_subscriptions(message.topic) for sub in subs: if sub['sync'] is True: self.log.debug("incoming message for synced topic %s" % message.topic) self.__sync_message_queues[message.topic].put(payload['content']) if 'callback' in sub and sub['callback'] is not None: callback = sub['callback'] callback(message.topic, payload['content']) if len(subs) == 0: self.log.warning("Incoming message for unhandled topic '%s'" % message.topic)
def serve(self): # load hooks settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") if os.path.exists(settings_file): with open(settings_file, 'r') as f: self.__hooks = loads(f.read()) # load registered handlers for entry in pkg_resources.iter_entry_points("gosa.webhook_handler"): module = entry.load() self.__handlers[entry.name] = module() # override for development mode monitor_key = self.env.config.get("webhooks.ldap_monitor_token") if monitor_key: if 'application/vnd.gosa.event+xml' not in self.__hooks: self.__hooks['application/vnd.gosa.event+xml'] = {} self.__hooks['application/vnd.gosa.event+xml']['backend-monitor'] = monitor_key
def __call__(self, *args, **kwargs): data = {} if '__user__' in kwargs: data['user'] = kwargs['__user__'] del kwargs['__user__'] if '__session_id__' in kwargs: data['session_id'] = kwargs['__session_id__'] del kwargs['__session_id__'] if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException( "JSON-RPC does not support positional and keyword arguments at the same time" ) # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send data.update({ "method": self.__serviceName, "id": "jsonrpc", "sender": self.env.uuid }) if len(kwargs): data["params"] = kwargs else: data["params"] = args postdata = dumps(data) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) return resp['result']
def serve(self): # load hooks settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") if os.path.exists(settings_file): with open(settings_file, 'r') as f: self.__hooks = loads(f.read()) # load registered handlers for entry in pkg_resources.iter_entry_points("gosa.webhook_handler"): module = entry.load() self.__handlers[entry.name] = module() # override for development mode monitor_key = self.env.config.get("webhooks.ldap_monitor_token") if monitor_key: if 'application/vnd.gosa.event+xml' not in self.__hooks: self.__hooks['application/vnd.gosa.event+xml'] = {} self.__hooks['application/vnd.gosa.event+xml'][ 'backend-monitor'] = monitor_key
def completeU2FRegistration(self, user_name, object_dn, data): # Do we have write permissions for the requested attribute self.__check_acl(user_name, object_dn, "w") user = ObjectProxy(object_dn) user_settings = self.__settings[user.uuid] data = loads(data) binding, cert = complete_register(user_settings.pop('_u2f_enroll_'), data, [self.facet]) devices = [DeviceRegistration.wrap(device) for device in user_settings.get('_u2f_devices_', [])] devices.append(binding) user_settings['_u2f_devices_'] = [d.json for d in devices] self.__save_settings() self.__log.info("U2F device enrolled. Username: %s", user_name) self.__log.debug("Attestation certificate:\n%s", cert.public_bytes(Encoding.PEM)) return True
def __on_message(self, client, userdata, message): payload = loads(message.payload) if self.__sender_id is not None and payload[ 'sender_id'] == self.__sender_id: # skip own messages return subs = self.get_subscriptions(message.topic) for sub in subs: if sub['sync'] is True: self.log.debug("incoming message for synced topic %s" % message.topic) self.__sync_message_queues[message.topic].put( payload['content']) if 'callback' in sub and sub['callback'] is not None: callback = sub['callback'] callback(message.topic, payload['content']) if len(subs) == 0: self.log.warning("Incoming message for unhandled topic '%s'" % message.topic)
def test_logout(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({"id": 3, "method": "logout", "params": []}) response = self.fetch('/rpc', method='POST', body=data) # logging out before beeing logged in is not allowed assert response.code == 401 self.login() response = self.fetch('/rpc', method='POST', body=data) assert response.code == 200 json = loads(response.body) assert json['result'] is True assert json['error'] is None assert json['id'] == 3 # check if we are logged out data = dumps({"id": 3, "method": "getSessionUser", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 401
def handle_request(self, request_handler): foreman = PluginRegistry.getInstance("Foreman") self.log.debug(request_handler.request.body) data = loads(request_handler.request.body) if data["action"] in ForemanRealmReceiver.skip_next_event: del ForemanRealmReceiver.skip_next_event[data["action"]] return # TODO disable hook logging to file with open("foreman-log.json", "a") as f: f.write("%s,\n" % dumps(data, indent=4, sort_keys=True)) ForemanBackend.modifier = "foreman" if data['action'] == "create": # new client -> join it try: key = yield foreman.add_host(data['hostname']) # send key as otp to foremans realm proxy request_handler.finish(dumps({ "randompassword": key })) except Exception as e: request_handler.finish(dumps({ "error": "%s" % e })) raise e elif data['action'] == "delete": try: foreman.remove_type("ForemanHost", data['hostname']) except Exception as e: request_handler.finish(dumps({ "error": "%s" % e })) raise e ForemanBackend.modifier = None
def __handle_result(self, response): """ Handle the results of the different login steps (login, 2FA, U2F) and process with the next required step until the login process succeeds or fails. """ try: result_code = int(response['state']) if result_code == AUTH_FAILED: print(_("Login of user '%s' failed") % self.__username) sys.exit(1) elif result_code == AUTH_OTP_REQUIRED: key = input(_("OTP-Passkey: ")) return self.__handle_result(self.proxy.verify(key)) elif result_code == AUTH_U2F_REQUIRED and 'u2f_data' in response: for device in u2f.list_devices(): with device as dev: data = loads(response['u2f_data']) print(data) print(_("Please touch the flashing U2F device now.")) for request in data['authenticateRequests']: data = u2f.authenticate(device, request, request['appId']) res = self.proxy.verify(data) if 'counter' in res and 'touch' in res: return True return False elif result_code == AUTH_SUCCESS: return True except Exception as e: print(e) sys.exit(1) return False
def __handle_result(self, response): """ Handle the results of the different login steps (login, 2FA, U2F) and process with the next required step until the login process succeeds or fails. """ try: print(response) result_code = int(response['state']) if result_code == AUTH_FAILED: print(_("Login of user '%s' failed") % self.__username) sys.exit(1) elif result_code == AUTH_OTP_REQUIRED: key = input(_("OTP-Passkey: ")) return self.__handle_result(self.proxy.verify(key)) elif result_code == AUTH_U2F_REQUIRED and 'u2f_data' in response: for device in u2f.list_devices(): with device as dev: data = loads(response['u2f_data']) print(data) print(_("Please touch the flashing U2F device now.")) for request in data['authenticateRequests']: data = u2f.authenticate(device, request, request['appId']) res = self.proxy.verify(data) if 'counter' in res and 'touch' in res: return True return False elif result_code == AUTH_SUCCESS: return True except Exception as e: print(e) sys.exit(1) return False
def setPasswordRecoveryAnswers(self, user, object_dn, data): """ Set the password recovery answers for a user """ data = loads(data) # Do we have read permissions for the requested attribute env = Environment.getInstance() topic = "%s.objects.%s.attributes.%s" % (env.domain, "User", "passwordRecoveryHash") aclresolver = PluginRegistry.getInstance("ACLResolver") if not aclresolver.check(user, topic, "w", base=object_dn): self.__log.debug( "user '%s' has insufficient permissions to write %s on %s, required is %s:%s" % (user, "isLocked", object_dn, topic, "w")) raise ACLException( C.make_error('PERMISSION_ACCESS', topic, target=object_dn)) user = ObjectProxy(object_dn) method = user.passwordMethod # Try to detect the responsible password method-class pwd_o = self.get_method_by_method_type(method) if not pwd_o: raise PasswordException( C.make_error("PASSWORD_UNKNOWN_HASH", type=method)) # hash the new answers for idx, answer in data.items(): data[idx] = pwd_o.generate_password_hash(self.clean_string(answer), method) print("%s encrypted with %s as index %s => %s" % (self.clean_string(answer), method, idx, data[idx])) # Set the password and commit the changes user.passwordRecoveryHash = dumps(data) user.commit()
def test_logout(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 3, "method": "logout", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) # logging out before beeing logged in is not allowed assert response.code == 401 self.login() response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 200 json = loads(response.body) assert json['result'] is True assert json['error'] is None assert json['id'] == 3 # check if we are logged out data = dumps({ "id": 3, "method": "getSessionUser", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 401
def __on_message(self, client, userdata, message): self.log.debug("%s: __on_message client='%s', userdata='%s', message='%s'" % (self.get_identifier(), client, userdata, message.payload)) payload = loads(message.payload) if isinstance(payload, dict) and "content" in payload: content = payload["content"] if self.__sender_id is not None and 'sender_id' in payload and payload['sender_id'] == self.__sender_id: # skip own messages return else: content = payload subs = self.get_subscriptions(message.topic) for sub in subs: if sub['sync'] is True: self.log.debug("%s: incoming message for synced topic %s" % (self.get_identifier(), message.topic)) self.__sync_message_queues[message.topic].put(content) if 'callback' in sub and sub['callback'] is not None: callback = sub['callback'] callback(message.topic, content) elif sub['sync'] is not True: self.log.warning("Incoming message not processed by async subscription because of missing callback") if len(subs) == 0: self.log.warning("Incoming message for unhandled topic '%s'" % message.topic)
def completeU2FRegistration(self, user_name, object_dn, data): # Do we have write permissions for the requested attribute self.__check_acl(user_name, object_dn, "w") user = ObjectProxy(object_dn) user_settings = self.__settings[user.uuid] data = loads(data) binding, cert = complete_register(user_settings.pop('_u2f_enroll_'), data, [self.facet]) devices = [ DeviceRegistration.wrap(device) for device in user_settings.get('_u2f_devices_', []) ] devices.append(binding) user_settings['_u2f_devices_'] = [d.json for d in devices] self.__save_settings() self.__log.info("U2F device enrolled. Username: %s", user_name) self.__log.debug("Attestation certificate:\n%s", cert.public_bytes(Encoding.PEM)) return True
def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException( "JSON-RPC does not support positional and keyword arguments at the same time" ) # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send if len(kwargs): postdata = dumps({ "method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc' }) else: postdata = dumps({ "method": self.__serviceName, 'params': args, 'id': 'jsonrpc' }) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) raise gen.Return(response)
def __call__(self, *args, **kwargs): data = {} if '__user__' in kwargs: data['user'] = kwargs['__user__'] del kwargs['__user__'] if '__session_id__' in kwargs: data['session_id'] = kwargs['__session_id__'] del kwargs['__session_id__'] # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send data.update({ "method": self.__serviceName, "id": "mqttrpc", "sender": self.env.uuid }) data["kwparams"] = kwargs data["params"] = args postdata = dumps(data) response = yield self.__handler.send_sync_message(postdata, topic, qos=2) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) return resp['result']
def parse_event(self, rawEvent): """ Parse the SSE event and call the on_event method with the created event dict :param str rawEvent: the raw string retrieved from the SSE server """ event = Event() for line in rawEvent.strip().splitlines(): (field, value) = line.decode().split(":", 1) field = field.strip() if field == "data": try: data = loads(value.strip()) except JSONDecodeError as e: # no json just use string data = value.strip() if event.data is None: event.data = data else: event.data = "%s\n%s" % (event.data, data) elif field == "id": event.id = value.strip() elif field == "event": event.name = value.strip() self.on_event(event)
def __reload(self): if not os.path.exists(self.settings_file): self.__save_settings() else: with open(self.settings_file, "r") as f: self.__settings = loads(f.read())
def requestPasswordReset(self, uid, step, uuid=None, data=None): """ Request a password reset if the submitted password recovery answers match the stored ones for the given user :param uid: user id :param step: 'start' to trigger the password reset process by sending an email with activation link to the user :param uuid: the recovery uuid :param data: optional data required by the current step :return: * """ # check for existing uid and status of the users password settings index = PluginRegistry.getInstance("ObjectIndex") res = index.search({'uid': uid, '_type': 'User'}, {'dn': 1}) if len(res) == 0: raise PasswordException(C.make_error("UID_UNKNOWN", target=uid)) dn = res[0]['dn'] user = ObjectProxy(dn) if user.mail is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_IMPOSSIBLE")) recovery_state = loads( user.passwordRecoveryState ) if user.passwordRecoveryState is not None else {} if step != "start": # check uuid if 'uuid' not in recovery_state or recovery_state['uuid'] != uuid: # recovery process has not been started raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) if step == "start": # start process by generating an unique password recovery link for this user and sending it to him via mail if 'uuid' not in recovery_state: # generate a new id recovery_state['sent_counter'] = 0 recovery_state['uuid'] = str(Uuid.uuid4()) recovery_state['state'] = 'started' # send the link to the user content = N_( "Please open this link to continue your password recovery process." ) + ":" gui = PluginRegistry.getInstance("HTTPService").get_gui_uri() content += "\n\n%s?pwruid=%s&uid=%s\n\n" % ( "/".join(gui), recovery_state['uuid'], uid) mail = PluginRegistry.getInstance("Mail") mail.send(user.mail, N_("Password recovery link"), content) recovery_state["sent_counter"] += 1 user.passwordRecoveryState = dumps(recovery_state) user.commit() return True elif step == "get_questions": # check correct state if recovery_state['state'] is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) # return the indices of the questions the user has answered recovery_hashes = loads(user.passwordRecoveryHash) # TODO retrieve minimum amount of correct answers from user policy object return random.sample(recovery_hashes.keys(), 3) elif step == "check_answers": # check correct state if recovery_state['state'] is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) data = loads(data) recovery_hashes = loads(user.passwordRecoveryHash) correct_answers = 0 for idx, answer in data.items(): if idx not in recovery_hashes: # the user hasn't answered this question continue # detect method from existing answer pwd_o = self.detect_method_by_hash(recovery_hashes[idx]) if not pwd_o: raise PasswordException( C.make_error("PASSWORD_RECOVERY_IMPOSSIBLE")) # encrypt and compare new answer if pwd_o.compare_hash(self.clean_string(answer), recovery_hashes[idx]): correct_answers += 1 # TODO retrieve minimum amount of correct answers from user policy object if correct_answers >= 3: recovery_state['state'] = 'verified' user.passwordRecoveryState = dumps(recovery_state) user.commit() return True else: return False elif step == "change_password": # check correct state if recovery_state['state'] != 'verified': raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) self.setUserPassword(uid, user.dn, data) user.passwordRecoveryState = None user.commit() return True
def __init__(self): self.env = Environment.getInstance() settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") if os.path.exists(settings_file): with open(settings_file, 'r') as f: self.__hooks = loads(f.read())
def writePPD(self, printer_cn, server_ppd_file, custom_ppd_file, data): if self.client is None: return server_ppd = None dir = self.env.config.get("cups.spool", default="/tmp/spool") if not os.path.exists(dir): os.makedirs(dir) try: server_ppd = self.client.getServerPPD(server_ppd_file) is_server_ppd = True ppd = cups.PPD(server_ppd) except Exception as e: self.log.error(str(e)) is_server_ppd = False if custom_ppd_file is not None: ppd = cups.PPD(os.path.join(dir, custom_ppd_file)) else: raise PPDException(C.make_error('COULD_NOT_READ_SOURCE_PPD')) if isinstance(data, str): data = loads(data) # apply options for option_name, value in data.items(): option = ppd.findOption(option_name) if option is not None: conflicts = ppd.markOption(option_name, value) if conflicts > 0: raise PPDException(C.make_error('OPTION_CONFLICT', option=option_name, value=value, conflicts=conflicts)) else: raise PPDException(C.make_error('OPTION_NOT_FOUND', option=option_name)) # calculate hash value for new PPD temp_file = tempfile.NamedTemporaryFile(delete=False) try: with open(temp_file.name, "w") as tf: ppd.writeFd(tf.fileno()) with open(temp_file.name, "r") as tf: result = tf.read() hash = hashlib.md5(repr(result).encode('utf-8')).hexdigest() index = PluginRegistry.getInstance("ObjectIndex") new_file = os.path.join(dir, "%s.ppd" % hash) if new_file == custom_ppd_file: # nothing to to return {} if not is_server_ppd: # check if anyone else is using a file with this hash value and delete the old file if not query = {"_type": "GotoPrinter", "gotoPrinterPPD": "%s.ppd" % hash} if printer_cn is not None: query["not_"] = {"cn": printer_cn} res = index.search(query, {"dn": 1}) if len(res) == 0: # delete file os.unlink(custom_ppd_file) with open(new_file, "w") as f: f.write(result) return { "gotoPrinterPPD": ["%s/ppd/modified/%s.ppd" % (get_server_url(), hash)], "configured": [True] } except Exception as e: self.log.error(str(e)) return {} finally: os.unlink(temp_file.name) if server_ppd is not None: os.unlink(server_ppd)
def __collect_user_configuration(self, client_id, users): """ :param client_id: deviceUUID or hostname :param users: list of currently logged in users on the client """ if isinstance(client_id, ObjectProxy): client = client_id else: client = self.__open_device(client_id, read_only=True) group = None index = PluginRegistry.getInstance("ObjectIndex") res = index.search({"_type": "GroupOfNames", "member": client.dn}, {"dn": 1}) if len(res) > 0: group = ObjectProxy(res[0]["dn"], read_only=True) config = {} resolution = None if group is not None and group.is_extended_by("GotoEnvironment") and group.gotoXResolution is not None: resolution = group.gotoXResolution if client.is_extended_by("GotoEnvironment") and client.gotoXResolution is not None: resolution = client.gotoXResolution release = None if client.is_extended_by("GotoMenu"): release = client.getReleaseName() elif group is not None and group.is_extended_by("ForemanHostGroup"): release = group.getReleaseName() parent_group = group while release is None and parent_group is not None and parent_group.parent_id is not None: res = index.search({"_type": "GroupOfNames", "extension": "ForemanHostGroup", "foremanGroupId": parent_group.parent_id}, {"dn": 1}) if len(res) == 0: break else: parent_group = ObjectProxy(res[0]["dn"], read_only=True) release = parent_group.getReleaseName() if release is None: self.log.error("no release found for client/user combination (%s/%s)" % (client_id, users)) client_menu = None if hasattr(client, "gotoMenu") and client.gotoMenu is not None: client_menu = loads(client.gotoMenu) # collect users DNs query_result = index.search({"_type": "User", "uid": {"in_": users}}, {"dn": 1}) for entry in query_result: user = ObjectProxy(entry["dn"], read_only=True) config[user.uid] = {} if release is not None: menus = [] if client_menu is not None: menus.append(client_menu) # get all groups the user is member of which have a menu for the given release query = {'_type': 'GroupOfNames', "member": user.dn, "extension": "GotoMenu", "gotoLsbName": release} for res in index.search(query, {"gotoMenu": 1}): # collect user menus for m in res.get("gotoMenu", []): menus.append(loads(m)) if len(menus): user_menu = None for menu_entry in menus: if user_menu is None: user_menu = self.get_submenu(menu_entry) else: self.merge_submenu(user_menu, self.get_submenu(menu_entry)) config[user.uid]["menu"] = user_menu # collect printer settings for user, starting with the clients printers settings = self.__collect_printer_settings(group) printer_names = [x["cn"] for x in settings["printers"]] # get all GroupOfNames with GotoEnvironment the user or client is member of for res in index.search({'_type': 'GroupOfNames', "member": {"in_": [user.dn, client.dn]}, "extension": "GotoEnvironment"}, {"dn": 1}): user_group = ObjectProxy(res["dn"], read_only=True) if group is not None and user_group.dn == group.dn: # this group has already been handled continue s = self.__collect_printer_settings(user_group) if user_group.gotoXResolution is not None: resolution = user_group.gotoXResolution for p in s["printers"]: if p["cn"] not in printer_names: settings["printers"].append(p) printer_names.append(p["cn"]) if s["defaultPrinter"] is not None: settings["defaultPrinter"] = s["defaultPrinter"] # override group environment settings if the client has one s = self.__collect_printer_settings(client) if len(s["printers"]) > 0: settings["printers"] = s["printers"] settings["defaultPrinter"] = s["defaultPrinter"] if user.is_extended_by("GosaAccount") and user.gosaDefaultPrinter is not None: # check if the users default printer is send to the client found = False for printer_settings in settings["printers"]: if printer_settings["cn"] == user.gosaDefaultPrinter: found = True break def process(res): if len(res) == 0: self.log.warning("users defaultPrinter not found: %s" % user.gosaDefaultPrinter) return None elif len(res) == 1: # add this one to the result set printer = ObjectProxy(res[0]["dn"], read_only=True) p_conf = {} for attr in self.printer_attributes: p_conf[attr] = getattr(printer, attr) return p_conf return False if found is False: # find the printer and add it to the settings res = index.search({"_type": "GotoPrinter", "cn": user.gosaDefaultPrinter}, {"dn": 1}) printer_config = process(res) if printer_config is False: # more than 1 printers found by this CN, try to look in the users subtree res = index.search({ "_type": "GotoPrinter", "cn": user.gosaDefaultPrinter, "_adjusted_parent_dn": user.get_adjusted_parent_dn() }, {"dn": 1}) printer_config = process(res) if isinstance(printer_config, dict): settings["printers"].append(printer_config) settings["defaultPrinter"] = user.gosaDefaultPrinter else: self.log.warning("users defaultPrinter not found: %s" % user.gosaDefaultPrinter) else: settings["defaultPrinter"] = user.gosaDefaultPrinter config[user.uid]["printer-setup"] = settings config[user.uid]["resolution"] = None if resolution is not None: config[user.uid]["resolution"] = [int(x) for x in resolution.split("x")] # TODO: collect and send login scripts to client return config
def handle_request(self, request_handler): foreman = PluginRegistry.getInstance("Foreman") data = loads(request_handler.request.body) self.log.debug(data) # TODO disable hook logging to file with open("foreman-log.json", "a") as f: f.write("%s,\n" % dumps(data, indent=4, sort_keys=True)) if data["event"] in ForemanHookReceiver.skip_next_event and data["object"] in ForemanHookReceiver.skip_next_event[data["event"]]: ForemanHookReceiver.skip_next_event[data["event"]].remove(data["object"]) self.log.info("skipped '%s' event for object: '%s'" % (data["event"], data["object"])) return data_keys = list(data['data'].keys()) if len(data_keys) == 1: type = data_keys[0] else: # no type given -> skipping this event as other might come with more information self.log.warning("skipping event '%s' for object '%s' as no type information is given in data: '%s'" % (data["event"], data["object"], data["data"])) return # search for real data if len(data['data'][type].keys()) == 1: # something like {data: 'host': {host: {...}}} # or {data: 'discovered_host': {host: {...}}} payload_data = data['data'][type][list(data['data'][type].keys())[0]] else: payload_data = data['data'][type] if type == "operatingsystem": with make_session() as session: foreman.sync_release_name(payload_data, session, event=data['event']) session.commit() return factory = ObjectFactory.getInstance() foreman_type = type if type == "discovered_host": type = "host" object_types = factory.getObjectNamesWithBackendSetting("Foreman", "type", "%ss" % type) object_type = object_types[0] if len(object_types) else None backend_attributes = factory.getObjectBackendProperties(object_type) if object_type is not None else None self.log.debug("Hookevent: '%s' for '%s' (%s)" % (data['event'], data['object'], object_type)) uuid_attribute = None if "Foreman" in backend_attributes: uuid_attribute = backend_attributes["Foreman"]["_uuidSourceAttribute"] \ if '_uuidSourceAttribute' in backend_attributes["Foreman"] else backend_attributes["Foreman"]["_uuidAttribute"] ForemanBackend.modifier = "foreman" update_data = {} if data['event'] in ["update", "create"] and foreman_type == "host": id = payload_data["id"] if "id" in payload_data else None try: foreman.write_parameters(id if id is not None else data['object']) except: foreman.mark_for_parameter_setting(data['object'], { "status": "created", "use_id": id }) if data['event'] == "after_commit" or data['event'] == "update" or data['event'] == "after_create" or data['event'] == "create": host = None if data['event'] == "update" and foreman_type == "host" and "mac" in payload_data and payload_data["mac"] is not None: # check if we have an discovered host for this mac index = PluginRegistry.getInstance("ObjectIndex") res = index.search({ "_type": "Device", "extension": ["ForemanHost", "ieee802Device"], "macAddress": payload_data["mac"], "status": "discovered" }, {"dn": 1}) if len(res): self.log.debug("update received for existing host with dn: %s" % res[0]["dn"]) host = ObjectProxy(res[0]["dn"]) if foreman_type != "discovered_host" and host.is_extended_by("ForemanHost"): host.status = "unknown" foreman_object = foreman.get_object(object_type, payload_data[uuid_attribute], create=host is None) if foreman_object and host: if foreman_object != host: self.log.debug("using known host instead of creating a new one") # host is the formerly discovered host, which might have been changed in GOsa for provisioning # so we want to use this one, foreman_object is the joined one, so copy the credentials from foreman_object to host if not host.is_extended_by("RegisteredDevice"): host.extend("RegisteredDevice") if not host.is_extended_by("simpleSecurityObject"): host.extend("simpleSecurityObject") host.deviceUUID = foreman_object.deviceUUID host.userPassword = foreman_object.userPassword host.otp = foreman_object.otp host.cn = foreman_object.cn # now delete the formerly joined host foreman_object.remove() foreman_object = host elif foreman_object is None and host is not None: foreman_object = host elif foreman_type == "discovered_host": self.log.debug("setting discovered state for %s" % payload_data[uuid_attribute]) if not foreman_object.is_extended_by("ForemanHost"): foreman_object.extend("ForemanHost") foreman_object.status = "discovered" if foreman_type == "host": old_build_state = foreman_object.build foreman.update_type(object_type, foreman_object, payload_data, uuid_attribute, update_data=update_data) if foreman_type == "host" and old_build_state is True and foreman_object.build is False and \ foreman_object.status == "ready": # send notification e = EventMaker() ev = e.Event(e.Notification( e.Title(N_("Host ready")), e.Body(N_("Host '%s' has been successfully build." % foreman_object.cn)), e.Icon("@Ligature/pc"), e.Timeout("10000") )) event_object = objectify.fromstring(etree.tostring(ev, pretty_print=True).decode('utf-8')) SseHandler.notify(event_object) elif data['event'] == "after_destroy": # print("Payload: %s" % payload_data) foreman.remove_type(object_type, payload_data[uuid_attribute]) # because foreman sends the after_commit event after the after_destroy event # we need to skip this event, otherwise the host would be re-created if "after_commit" not in ForemanHookReceiver.skip_next_event: ForemanHookReceiver.skip_next_event["after_commit"] = [data['object']] else: ForemanHookReceiver.skip_next_event["after_commit"].append(data['object']) # add garbage collection for skip sobj = PluginRegistry.getInstance("SchedulerService") sobj.getScheduler().add_date_job(self.cleanup_event_skipper, datetime.datetime.now() + datetime.timedelta(minutes=1), args=("after_commit", data['object']), tag='_internal', jobstore='ram') else: self.log.info("unhandled hook event '%s' received for '%s'" % (data['event'], type)) ForemanBackend.modifier = None
for path, dirs, files in os.walk("src/gosa/backend/data"): for f in files: data_files.append(os.path.join(path[17:], f)) if sys.argv[1] == "import-from-json": from gosa.common.gjson import loads # import old template translations from json files translations = {} for translation_file in glob.glob(os.path.join("src", "gosa", "backend", "data", "templates", "i18n", "*", "*.json")): lang = os.path.basename(translation_file).split(".")[0] if lang == "en": continue if lang not in translations: translations[lang] = {} with open(translation_file) as f: translations[lang].update(loads(f.read())) # write them to the PO-Files for lang, strings in translations.items(): po_file = os.path.join("src", "gosa", "backend", "locale", lang, "LC_MESSAGES", "messages.po") if os.path.exists(po_file): po = polib.pofile(po_file) changed = False for key, translation in strings.items(): if translation is None: continue entry = po.find(key) if entry is not None and entry.translated() is False: entry.msgstr = translation changed = True
def writePPD(self, printer_cn, server_ppd_file, custom_ppd_file, data): if self.client is None: return server_ppd = None dir = self.env.config.get("cups.spool", default="/tmp/spool") if not os.path.exists(dir): os.makedirs(dir) try: server_ppd = self.client.getServerPPD(server_ppd_file) is_server_ppd = True ppd = cups.PPD(server_ppd) except Exception as e: self.log.error(str(e)) is_server_ppd = False if custom_ppd_file is not None: ppd = cups.PPD(os.path.join(dir, custom_ppd_file)) else: raise PPDException(C.make_error('COULD_NOT_READ_SOURCE_PPD')) if isinstance(data, str): data = loads(data) # apply options for option_name, value in data.items(): option = ppd.findOption(option_name) if option is not None: conflicts = ppd.markOption(option_name, value) if conflicts > 0: raise PPDException( C.make_error('OPTION_CONFLICT', option=option_name, value=value, conflicts=conflicts)) else: raise PPDException( C.make_error('OPTION_NOT_FOUND', option=option_name)) # calculate hash value for new PPD temp_file = tempfile.NamedTemporaryFile(delete=False) try: with open(temp_file.name, "w") as tf: ppd.writeFd(tf.fileno()) with open(temp_file.name, "r") as tf: result = tf.read() hash = hashlib.md5(repr(result).encode('utf-8')).hexdigest() index = PluginRegistry.getInstance("ObjectIndex") new_file = os.path.join(dir, "%s.ppd" % hash) if new_file == custom_ppd_file: # nothing to to return {} if not is_server_ppd: # check if anyone else is using a file with this hash value and delete the old file if not query = { "_type": "GotoPrinter", "gotoPrinterPPD": "%s.ppd" % hash } if printer_cn is not None: query["not_"] = {"cn": printer_cn} res = index.search(query, {"dn": 1}) if len(res) == 0: # delete file os.unlink(custom_ppd_file) with open(new_file, "w") as f: f.write(result) return { "gotoPrinterPPD": ["%s/ppd/modified/%s.ppd" % (get_server_url(), hash)], "configured": [True] } except Exception as e: self.log.error(str(e)) return {} finally: os.unlink(temp_file.name) if server_ppd is not None: os.unlink(server_ppd)
def process(self, data): """ Process an incoming JSONRPC request and dispatch it thru the *CommandRegistry*. ================= ========================== Parameter Description ================= ========================== data Incoming body data ================= ========================== ``Return``: varries """ try: json = loads(data) except ValueError as e: raise ValueError(C.make_error("INVALID_JSON", data=str(e))) try: method = json['method'] params = json['params'] jid = json['id'] except KeyError as e: raise ValueError(C.make_error("JSON_MISSING_PARAMETER")) if method.startswith('_'): raise tornado.web.HTTPError(403, "Bad method name %s: must not start with _" % method) if not isinstance(params, list) and not isinstance(params, dict): raise ValueError(C.make_error("PARAMETER_LIST_OR_DICT")) # Check if we're globally locked currently if GlobalLock.exists("scan_index"): raise FilterException(C.make_error('INDEXING', "base")) # execute command if it is allowed without login if method in no_login_commands: return self.dispatch(method, params, jid) cls = self.__class__ twofa_manager = PluginRegistry.getInstance("TwoFactorAuthManager") # Create an authentication cookie on login if method == 'login': (user, password) = params # Check password and create session id on success sid = str(uuid.uuid1()) result = { 'state': AUTH_FAILED } lock_result = self.get_lock_result(user) if lock_result is not None: return dict(result=lock_result, error=None, id=jid) dn = self.authenticate(user, password) if dn is not False: # user and password matches so delete the user from observer list if user in cls.__dos_manager: del cls.__dos_manager[user] cls.__session[sid] = { 'user': user, 'dn': dn, 'auth_state': None } self.set_secure_cookie('REMOTE_USER', user) self.set_secure_cookie('REMOTE_SESSION', sid) factor_method = twofa_manager.get_method_from_user(dn) if factor_method is None: result['state'] = AUTH_SUCCESS self.log.info("login succeeded for user '%s'" % user) elif factor_method == "otp": result['state'] = AUTH_OTP_REQUIRED self.log.info("login succeeded for user '%s', proceeding with OTP two-factor authentication" % user) elif factor_method == "u2f": self.log.info("login succeeded for user '%s', proceeding with U2F two-factor authentication" % user) result['state'] = AUTH_U2F_REQUIRED result['u2f_data'] = twofa_manager.sign(user, dn) cls.__session[sid]['auth_state'] = result['state'] else: # Remove current sid if present if not self.get_secure_cookie('REMOTE_SESSION') and sid in cls.__session: del cls.__session[sid] self.log.error("login failed for user '%s'" % user) result['state'] = AUTH_FAILED # log login tries if user in cls.__dos_manager: login_stats = cls.__dos_manager[user] # stop counting after 6 tries to avoid "infinity" lock, the user is locked for more than an hour if login_stats['count'] < 6: login_stats['count'] += 1 login_stats['timestamp'] = time.time() cls.__dos_manager[user] = login_stats else: cls.__dos_manager[user] = { 'count': 1, 'timestamp': time.time(), 'ip': self.request.remote_ip } lock_result = self.get_lock_result(user) if lock_result is not None: return dict(result=lock_result, error=None, id=jid) return dict(result=result, error=None, id=jid) # Don't let calls pass beyond this point if we've no valid session ID if self.get_secure_cookie('REMOTE_SESSION') is None or not self.get_secure_cookie('REMOTE_SESSION').decode('ascii') in cls.__session: self.log.error("blocked unauthenticated call of method '%s'" % method) raise tornado.web.HTTPError(401, "Please use the login method to authorize yourself.") # Remove remote session on logout if method == 'logout': # Remove current sid if present if self.get_secure_cookie('REMOTE_SESSION') and self.get_secure_cookie('REMOTE_SESSION').decode('ascii') in cls.__session: del cls.__session[self.get_secure_cookie('REMOTE_SESSION').decode('ascii')] # Show logout message if self.get_secure_cookie('REMOTE_USER'): self.log.info("logout for user '%s' succeeded" % self.get_secure_cookie('REMOTE_USER')) self.clear_cookie("REMOTE_USER") self.clear_cookie("REMOTE_SESSION") return dict(result=True, error=None, id=jid) # check two-factor authentication sid = self.get_secure_cookie('REMOTE_SESSION').decode('ascii') if method == 'verify': (key,) = params if cls.__session[sid]['auth_state'] == AUTH_OTP_REQUIRED or cls.__session[sid]['auth_state'] == AUTH_U2F_REQUIRED: if twofa_manager.verify(cls.__session[sid]['user'], cls.__session[sid]['dn'], key): cls.__session[sid]['auth_state'] = AUTH_SUCCESS return dict(result={'state': AUTH_SUCCESS}, error=None, id=jid) else: return dict(result={'state': AUTH_FAILED}, error=None, id=jid) if cls.__session[sid]['auth_state'] != AUTH_SUCCESS: raise tornado.web.HTTPError(401, "Please use the login method to authorize yourself.") return self.dispatch(method, params, jid)
def commandReceived(self, topic, message): """ Process incoming commands, coming in with session and message information. ================= ========================== Parameter Description ================= ========================== message Received MQTT message ================= ========================== Incoming messages are coming from an :class:`gosa.common.components.mqtt_proxy.MQTTServiceProxy`. The command result is written to the '<domain>.client.<client-uuid>' queue. """ err = None res = None name = None args = None id_ = '' response_topic = "%s/to-backend" % "/".join(topic.split("/")[0:4]) try: req = loads(message) except Exception as e: err = str(e) self.log.error("ServiceRequestNotTranslatable: %s" % err) req = {'id': topic.split("/")[-2]} if err is None: try: id_ = req['id'] name = req['method'] args = req['params'] except KeyError as e: self.log.error("KeyError: %s" % e) err = str(BadServiceRequest(message)) self.log.debug("received call [%s] for %s: %s(%s)" % (id_, topic, name, args)) # Try to execute if err is None: try: res = self.__cr.dispatch(name, *args) except Exception as e: err = str(e) # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error( traceback.format_exception(exc_type, exc_value, exc_traceback)) self.log.debug("returning call [%s]: %s / %s" % (id_, res, err)) response = dumps({"result": res, "id": id_}) # Get rid of it... mqtt = PluginRegistry.getInstance('MQTTClientHandler') mqtt.send_message(response, topic=response_topic)
def test_rpc(self): service = PluginRegistry.getInstance("MQTTRPCService") request_uuid = uuid.uuid4() topic = "%s/proxy/fake_client_id/%s" % ( Environment.getInstance().domain, request_uuid) with mock.patch.object(PluginRegistry.getInstance('CommandRegistry'), "dispatch") as m, \ mock.patch.object(service.mqtt, "send_message") as mq: m.return_value = "fake_response" # call with wrong json service.handle_request(topic, "this is no json: 'string'") args, kwargs = mq.call_args response = loads(args[0]) assert "error" in response assert kwargs["topic"] == "%s/response" % topic assert not m.called mq.reset_mock() # call without params service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******" })) args, kwargs = mq.call_args response = loads(args[0]) assert "error" in response assert kwargs["topic"] == "%s/response" % topic assert not m.called # call with empty params service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******", "session_id": "fake_session_id", "params": [] })) m.assert_called_with("admin", "fake_session_id", "fakeCall") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic mq.reset_mock() m.reset_mock() service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******", "params": ["param1", "param2"] })) m.assert_called_with("admin", None, "fakeCall", "param1", "param2") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic mq.reset_mock() m.reset_mock() # call without user (client id taken as user) service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "params": [] })) m.assert_called_with("fake_client_id", None, "fakeCall") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic