def ui_host_manager_add(host_uuid): host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') form = forms.HostAddManagerForm() if form.validate_on_submit(): email = form.email.data.lower() managers = models.Manager.query.filter( models.Manager.email.ilike(email)) if managers.first(): for manager in managers: if host in manager.hosts: abort(403, f"Host already managed by {html.escape(email)}") else: manager.hosts.append(host) else: # create new manager manager = models.Manager(email=email) db.session.add(manager) manager.hosts.append(host) db.commit_and_sync() return redirect(url_for("ui_host", host_uuid=host_uuid)) else: return render_template('host-manager-add.html', title=f'Add manager for {host.hostname}', host=host, form=form)
def ui_host_add(): try: form = forms.HostAddForm() if form.validate_on_submit(): hostname = form.hostname.data if not re.match('^[\w\-\.\ ]+$', hostname): abort(403, "Bad symbols in hostname") for host in current_user.hosts: if hostname.lower() == host.hostname.lower(): abort( 403, f"Host {html.escape(hostname)} already exists among your hosts" ) host = models.ManagedHost(hostname=hostname, uuid=new_host_uuid()) db.session.add(host) current_user.hosts.append(host) host.pin_generate() db.commit_and_sync() return render_template('host.html', title=host.hostname, host=host) else: return render_template('host-add.html', title='Add host', form=form) except TimekprwException as e: debug("exception:", exc_info=True) abort(403, str(e))
def rest_host_getauthkey(): """ REST: add host with pin and return auth key to client Authentication is not required :param { pin: "nnnnnn" } :return: { success: "true"|"false", message: "Cause of problem if success=false" (optional), authkey: assigned authentication key for future authentication hostuuid: host uuid } """ rv = { "success": False, "message": "Internal error: message is not defined" } data = request.get_json() if not data: return {**rv, "message": "json data expected"} if "pin" in data: # TODO: this implementation can be brute-forced # what we can do is to require some proof of work from the client pin = str(data["pin"]) hostqry = models.ManagedHost.query.filter_by(pin=pin) count = hostqry.count() if count == 0: return { **rv, "message": "Please provide correct pin, or obtain new pin from server" } if count > 1: return { **rv, "message": "Internal error, please get another pin and come back" } if count != 1: return { **rv, "message": f"Internal error on server, please report to developer: count={count} and it is not 0,1,>1" } host = hostqry.first() (authkey_plain, authkey_hash) = host.authkey_generate() host.authkey = authkey_hash host.authkey_trycount = 0 host.pin = None db.commit_and_sync() return { "success": True, "authkey": authkey_plain, "hostuuid": host.uuid } else: # "pin" is not in data return {**rv, "message": "pin expected"} return {**rv}
def ui_host_deactivate(host_uuid): """Clear authentication token and pin""" host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') host.pin = None host.authkey = None db.commit_and_sync() return redirect(url_for('ui_host', host_uuid=host_uuid))
def ui_host_user_rm(host_uuid, user_id): host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') user = models.ManagedUser.query.filter_by(id=user_id).first() if not user: abort(403, 'Did not find such user') if user.host != host: abort(403, 'This user does not belong to this host') host.users.remove(user) db.commit_and_sync() return redirect(url_for("ui_host", host_uuid=host_uuid))
def ui_host_manager_rm(host_uuid, manager_id): host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') manager = models.Manager.query.filter_by(id=manager_id).first() if not manager: abort(403, 'Did not find such manager') if manager == current_user: abort(403, 'You cannot remove yourself from managers list') if manager not in host.managers: abort(403, 'This manager does not manage this host') host.managers.remove(manager) db.commit_and_sync() return redirect(url_for("ui_host", host_uuid=host_uuid))
def ui_host_rm(host_uuid): host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') form = forms.HostRemoveForm() if form.validate_on_submit(): db.session.delete(host) db.commit_and_sync() return redirect(url_for("webroot")) else: return render_template('host-rm.html', title=f'Confirm remove {host.hostname}', host=host, form=form)
def ui_time(user_uuid): user_uuid = validate_uuid(user_uuid) user = models.ManagedUser.query.filter_by(uuid=user_uuid).first() if not user or current_user not in user.host.managers: abort(403, 'No user found with given id and managed by you') form = forms.TimeForm(useruuid=user_uuid, username=str(user)) if form.validate_on_submit(): amount = form.amount.data override = models.TimeOverride(amount=amount, status=models.TimeOverrideStatusQueued, user=user, owner=current_user) db.session.add(override) db.commit_and_sync() return render_template('time_added.html', user=user, amount=amount) else: return render_template('time.html', title='Time Override', form=form, username=str(user))
def rest_overrides_ack(host_uuid): """ REST: Acknowledge time overrides for host. Call after applying overrides on host. :param host_uuid: :return: { success: True } """ rv = { "success": False, "message": "Internal error: message is not defined" } host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host: return {**rv, "message": "No host found with given id"} data = request.get_json() if not data: return {**rv, "message": "json data expected"} auth_result = rest_check_auth(host, data) if not auth_result["success"]: return auth_result for user in host.users: # clean older overrides for override in [ x for x in user.timeoverrides if x.status != models.TimeOverrideStatusQueued ]: db.session.delete(override) # change status of last override for override in [ x for x in user.timeoverrides if x.status == models.TimeOverrideStatusQueued ]: override.status = models.TimeOverrideStatusApplied db.commit_and_sync() return {'success': True}
def ui_host_user_add(host_uuid): host_uuid = validate_uuid(host_uuid) host = models.ManagedHost.query.filter_by(uuid=host_uuid).first() if not host or current_user not in host.managers: abort(403, 'No host found with given id and managed by you') form = forms.HostAddUserForm() if form.validate_on_submit(): login = form.login.data # TODO: don't know how to query for users of this host with such login name existing = [x for x in host.users if x.login.lower() == login.lower()] if len(existing): abort(403, f"User {login} already exists on host {host.hostname}") user = models.ManagedUser(uuid=new_user_uuid(), login=login) db.session.add(user) host.users.append(user) db.commit_and_sync() return redirect(url_for("ui_host", host_uuid=host_uuid)) else: return render_template('host-user-add.html', title=f'Add user on {host.hostname}', host=host, form=form)
def authkey_check(self, authkey): """ Check authkey, return True or False may raise TimekprwException Performs DB commit """ try: if not self.authkey: return False rv = pbkdf2_sha512.verify(authkey, self.authkey) if not rv: if self.authkey_trycount is None: self.authkey_trycount = 0 self.authkey_trycount += 1 else: self.auth_lastsuccess = datetime.datetime.utcnow() db.commit_and_sync(sync_priority=SyncPriorityDelayed) return rv except Exception as e: debug(f'got exception in authkey_check: {e}, trace follows:', exc_info=True) raise TimekprwException('Failed to check authkey')
def app_init(): """Get DB from permanent storage if needed. Upgrade database if needed.""" debug(f"{whoami()} called") if db.db_permstore_instance: db.db_permstore_instance.get_from_permstore() fresh_db = False try: # baaad hack, but flask_migrate.current() returns nothing, and I don't know how to test if DB is newly created (i.e. sqlite), or existed before if not db.engine.dialect.has_table(db.engine, 'alembic_version'): # the only reason i know for this is when database is newly created # and we definitely should not run db.create_all() before migration! fresh_db = True except Exception as e: error( f"got exception while trying to get db revision: {e} (enable debug for more)" ) debug(f"exception follows:", exc_info=True) debug("running db.create_all()") db.create_all() debug("done db.create_all()") try: if fresh_db: info("This is a fresh database, marking it as latest revision") run_in_child_process(flask_migrate.stamp) else: debug("Trying to perform database migration") run_in_child_process(flask_migrate.upgrade) except Exception as e: error( f"got exception while trying to upgrade db revision: {e} (enable debug for more)" ) debug(f"exception follows:", exc_info=True) if os.environ.get('APP_ENVIRONMENT', None) == "dev": if models.Manager.query.count(): debug( "skipping creation of dev objects - database already has objects" ) else: debug("creating dev objects") host1 = models.ManagedHost(uuid=new_host_uuid(), hostname='testhost.tld') db.session.add(host1) host2 = models.ManagedHost(uuid=new_host_uuid(), hostname='otherhost.dom') db.session.add(host2) user1 = models.ManagedUser(uuid=new_user_uuid(), login="******", host=host1) db.session.add(user1) user2 = models.ManagedUser(uuid=new_user_uuid(), login="******", host=host1) db.session.add(user2) manager1 = models.Manager() manager1.hosts.append(host1) manager1.hosts.append(host2) db.session.add(manager1) db.commit_and_sync() debug("done creating dev objects") # TODO: remove this in production debug("creating built-in objects") host = models.ManagedHost.query.filter_by( uuid='59e8368c-7dbc-11ea-923e-7cb0c2957d37').first() if not host: debug("creating host") host = models.ManagedHost(uuid=str( uuid.UUID('59e8368c-7dbc-11ea-923e-7cb0c2957d37')), hostname='john') user = models.ManagedUser(uuid=new_user_uuid(), login='******', host=host) db.session.add(user) db.session.add(host) manager1 = models.Manager.query.filter_by( email='*****@*****.**').first() if not manager1: debug("creating manager [email protected]") manager1 = models.Manager( ext_auth_type=models.ExtAuthTypeGoogleAuth, ext_auth_id='118295366576899719337', email='*****@*****.**') manager1.hosts.append(host) db.session.add(manager1) #manager2 = models.Manager.query.filter_by(email='*****@*****.**').first() #if not manager2: # debug("creating manager [email protected]") # manager2 = models.Manager(ext_auth_type=models.ExtAuthTypeGoogleAuth, # ext_auth_id='103494272264223262600', email='*****@*****.**') # manager2.hosts.append(host) # db.session.add(manager2) db.commit_and_sync() debug("done creating built-in objects") # debug(dumpdata()) debug(f"{whoami()} finished")
def login_callback(): """Callback for Google Auth""" # Get authorization code Google sent back to you code = request.args.get("code") # Find out what URL to hit to get tokens that allow you to ask for # things on behalf of a user google_provider_cfg = get_google_provider_cfg() token_endpoint = google_provider_cfg["token_endpoint"] # Prepare and send a request to get tokens! Yay tokens! token_url, headers, body = client.prepare_token_request( token_endpoint, authorization_response=request.url, redirect_url=request.base_url, code=code) token_response = requests.post( token_url, headers=headers, data=body, auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), ) # Parse the tokens! client.parse_request_body_response(json.dumps(token_response.json())) # Now that you have tokens (yay) let's find and hit the URL # from Google that gives you the user's profile information, # including their Google profile image and email userinfo_endpoint = google_provider_cfg["userinfo_endpoint"] uri, headers, body = client.add_token(userinfo_endpoint) userinfo_response = requests.get(uri, headers=headers, data=body) # You want to make sure their email is verified. # The user authenticated with Google, authorized your # app, and now you've verified their email through Google! if userinfo_response.json().get("email_verified"): unique_id = userinfo_response.json()["sub"] users_email = userinfo_response.json()["email"].lower() # picture = userinfo_response.json()["picture"] users_name = userinfo_response.json()["given_name"] else: return "User email not available or not verified by Google.", 400 # Create a user in your db with the information provided by Google user = models.Manager.query.filter_by( ext_auth_id=unique_id, ext_auth_type=models.ExtAuthTypeGoogleAuth).first() if not user: # no user with given id, try to search by email among google-hosted users user = models.Manager.query.filter_by( email=users_email, ext_auth_type=models.ExtAuthTypeGoogleAuth).first() if not user: # no user with this email among google-hosted users, try to search by email with no type set user = models.Manager.query.filter( db.and_(models.Manager.email == users_email, models.Manager.ext_auth_type == None)).first() if user: # update user's data if we already have it if user.name != users_name: user.name = users_name if user.email != users_email: user.email = users_email if user.ext_auth_type != models.ExtAuthTypeGoogleAuth: user.ext_auth_type = models.ExtAuthTypeGoogleAuth # if user.picture != picture: user.picture = picture else: user = models.Manager(ext_auth_id=unique_id, ext_auth_type=models.ExtAuthTypeGoogleAuth, name=users_name, email=users_email) db.session.add(user) db.commit_and_sync(sync_priority=SyncPriorityDelayed) login_user(user, remember=True) return redirect(url_for("webroot"))