def kiosk_login(self) -> Union[str, Response]: if ( "kiosk_password" in session and session["kiosk_password"] == self.app.config["KIOSK_KEY"] ): return redirect(url_for("kiosk.kiosk_card")) if "User-Agent" in request.headers and request.headers.get( # type: ignore "User-Agent" ).startswith("Kiosk"): if "Key=" + self.app.config["KIOSK_KEY"] in request.headers.get( # type: ignore "User-Agent" ).split( " " ): session["kiosk_password"] = self.app.config["KIOSK_KEY"] return redirect(url_for("kiosk.kiosk_card")) else: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Invalid password. Login attempt has been logged.", ) ) else: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Invalid client. Access attempt has been logged.", ) ) return render_template("kiosk_login.html")
def enroll_member(self) -> Union[str, Response]: form = EnrollForm() if form.validate_on_submit(): if ( self.storage.session.query(Person) .filter_by(email=form.email.data) .scalar() ): self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "The specified email %s is already in use!" % form.email.data, ) ) elif ( form.username.data and self.storage.session.query(Person) .filter_by(username=form.username.data) .scalar() ): self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "The specified StiL %s is already in use!" % form.username.data, ) ) else: person = Person(enrolled_by=self.util.current_user()) form.populate_obj(person) self.storage.add(person) self.storage.commit() logging.info("Enrolled %s %s", person.fname, person.lname) self.mail.send_mail( person, "mail/member_enrolled.html", member=person) self.mail.send_mail( self.app.config["ORG_EMAIL"], "mail/board_member_enrolled.html", member=person, ) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "%s %s was successfully enrolled" % (form.fname.data, form.lname.data), ) ) return render_template("message.html") return render_template("enroll_member.html", form=form)
def kiosk_success(self) -> Union[str, Response]: if ( "kiosk_password" not in session or session["kiosk_password"] != self.app.config["KIOSK_KEY"] ): return redirect(url_for("kiosk.kiosk_login")) if "kiosk_uid" not in session: return redirect(url_for("kiosk.kiosk_card")) person = ( self.storage.session.query(Person) .filter_by(uid=session["kiosk_uid"]) .scalar() ) if not person: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Missing user %s" % (session["kiosk_uid"]), ) ) return redirect(url_for("kiosk.kiosk_card")) if "kiosk_uid" in session: session.pop("kiosk_uid") return render_template("kiosk_success.html", person=person)
def reset_password(self) -> Union[str, Response]: form_reset_password = ResetPasswordForm() if form_reset_password.validate_on_submit(): person: Person = ( self.storage.session.query(Person) .filter_by(email=form_reset_password.email.data) .scalar() ) if person: new_pass = ''.join( (random.choice(string.ascii_letters) for i in range(20))) person.password = self.auth.hash_password(new_pass) self.storage.commit() logging.info("Reset password for %s %s", person.fname, person.lname) self.mail.send_mail( person, "mail/reset_password.html", member=person, new_pass=new_pass) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Reset email sent", "If that email exists in the membership database, then an email has been sent to that address.", ) ) return render_template( "reset_password.html", form_reset_password=form_reset_password, )
def login(self) -> Union[str, Response]: form_login = LoginForm() if form_login.validate_on_submit(): person = ( self.storage.session.query(Person) .filter_by(email=form_login.email.data) .scalar() ) if ( person and self.auth.verify_password(person.password, form_login.password.data) ): self.util.start_session(person.uid) return redirect(request.form["redir"]) else: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Authentication error", "The username or password is incorrect.", ) ) return render_template( "login.html", redir=request.args.get("redir", url_for("index")), form_login=form_login, sso_providers=self.util.get_alternate_logins(), )
def kiosk_register(self) -> Union[str, Response]: if ( "kiosk_password" not in session or session["kiosk_password"] != self.app.config["KIOSK_KEY"] ): return redirect(url_for("kiosk.kiosk_login")) # Remove old user if they came here via "Logout" button if "kiosk_card" not in session: return redirect(url_for("kiosk.kiosk_card")) form = KioskRegisterForm() if form.validate_on_submit(): person: Person = ( self.storage.session.query(Person) .filter_by(username=form.username.data) .scalar() ) if person: person.card_id = session.pop("kiosk_card") self.storage.commit() self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Card verified successfully", "You can now use your student card in the Kiosk.", ) ) return redirect(url_for("kiosk.kiosk_card")) else: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Missing user account", "Could not find user '%s'. Please contact an ETF board member." % (form.username.data), ) ) return render_template("kiosk_register.html", form=form)
def membership_edit(self) -> Union[str, Response]: person: Person = (self.storage.session.query(Person).filter_by( uid=self.util.current_user()).scalar()) form_email = EditEmailForm() form_password = EditPasswordForm() if "submit_email" in request.form and form_email.validate(): person.email = form_email.email1.data self.storage.commit() logging.info("Changed email for %s %s", person.fname, person.lname) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Email updated", "Your email address has now been changed to %s." % (form_email.email1.data), )) if "submit_password" in request.form and form_password.validate(): person.password = self.auth.hash_password( form_password.password1.data) self.storage.commit() logging.info("Changed password for %s %s", person.fname, person.lname) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Password changed", "Your password has now been changed.", )) return render_template("membership_edit.html", person=person, form_email=form_email, form_password=form_password)
def member_groups(self, uid: int) -> Union[str, Response]: groups: Iterable[Group] = self.storage.session.query(Group).all() person: Person = self.storage.session.query(Person).filter_by( uid=uid).scalar() if not person: raise abort(404) if request.method == "POST": # Only enroll can add or remove groups if not self.auth.member_of("enroll") and not self.auth.member_of( "admin"): abort(403) for group in groups: # Only admin can add or remove admin! if not self.auth.member_of("admin") and group.name == "admin": continue current_state = self.auth.member_of(group, person) if sum(1 for gid in list(request.form.keys()) if gid == str(group.gid)): # Member of the group, add if needed if not current_state: logging.info("Adding group %s to %s %s", group.name, person.fname, person.lname) self.storage.add( PersonGroup(uid=person.uid, gid=group.gid)) else: # Not a member, remove if needed if current_state: for g in person.groups: if g.gid == group.gid: logging.info("Deleting group %s from %s %s", group.name, person.fname, person.lname) self.storage.delete(g) self.storage.commit() self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "Groups for %s %s was successfully updated" % (person.fname, person.lname), )) return render_template("user_groups.html", person=person, groups=groups)
def error_not_found(self, error: Exception) -> Tuple[Union[str, Response], int]: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Not Found", "The requested page or resource was not found. \ Koseki was therefore unable to produce a correct response. \ Please contact a member of the staff if the problem persists.", )) return ( render_template( "error.html", code=404, ), 404, )
def error_forbidden(self, error: Exception) -> Tuple[Union[str, Response], int]: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Forbidden", "You do not have permission to access this page. \ Koseki was therefore unable to produce a correct response. \ Please contact a member of the staff if the problem persists.", )) return ( render_template( "error.html", code=403, ), 403, )
def error_bad_request( self, error: Exception) -> Tuple[Union[str, Response], int]: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Bad Request", "The browser sent an invalid request, unable to be understood by the server. \ Koseki was therefore unable to produce a correct response. \ Please contact a member of the staff if the problem persists.", )) return ( render_template( "error.html", code=400, ), 400, )
def member_general(self, uid: int) -> Union[str, Response]: person: Person = self.storage.session.query(Person).filter_by( uid=uid).scalar() if not person: raise abort(404) form = GeneralForm(obj=person) if form.validate_on_submit(): form.populate_obj(person) person.reduce_empty_to_null() self.storage.commit() self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "%s %s was successfully updated" % (form.fname.data, form.lname.data), )) return render_template("user_general.html", form=form, person=person)
def kiosk_card(self) -> Union[str, Response]: if ( "kiosk_password" not in session or session["kiosk_password"] != self.app.config["KIOSK_KEY"] ): return redirect(url_for("kiosk.kiosk_login")) # Remove old user if they came here via "Logout" button if "kiosk_uid" in session: session.pop("kiosk_uid") if "kiosk_card" in session: session.pop("kiosk_card") form = KioskCardForm() if form.validate_on_submit() and len(form.card_id.data) == 10: person = ( self.storage.session.query(Person) .filter_by(card_id=form.card_id.data) .scalar() ) if person: session["kiosk_uid"] = person.uid return redirect(url_for("kiosk.kiosk_products")) else: self.util.alert( KosekiAlert( KosekiAlertType.WARNING, "Card not verified", "This is the first time you are using ETF Kiosk. Please verify by entering your student StiL/LUCAT.", ) ) session["kiosk_card"] = form.card_id.data return redirect(url_for("kiosk.kiosk_register")) form.card_id.data = "" return render_template("kiosk_card.html", form=form)
def list_products(self) -> Union[str, Response]: product_form = ProductForm() if product_form.submitAdd.data and product_form.validate_on_submit(): # Store product product = Product( name=product_form.name.data, img_url=product_form.img_url.data, price=product_form.price.data, order=product_form.order.data, ) self.storage.add(product) self.storage.commit() logging.info( "Registered product %s #%d", product_form.name.data, product.pid ) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "Registered product %s #%d" % ( product_form.name.data, product.pid), ) ) product_form = ProductForm(None) return render_template( "store_list_products.html", form=product_form, products=self.storage.session.query(Product) .order_by(Product.order.asc()) .all(), )
def register_fee(self) -> Union[str, Response]: fee_form = FeeForm() payment_form = PaymentForm() if "submitFee" in request.form and fee_form.validate(): payment_form = PaymentForm(None) # Clear the other form person = ( self.storage.session.query(Person) .filter_by(uid=fee_form.uid.data) .scalar() ) if person is None: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", 'No such member "%s". Did you try the auto-complete feature?' % fee_form.uid.data, ) ) return redirect(url_for("register_fee")) # Calculate period of validity last_fee: Fee = ( self.storage.session.query(Fee) .filter_by(uid=person.uid) .order_by(Fee.end.desc()) .first() ) if last_fee and last_fee.end > datetime.now(): # type: ignore logging.debug( "Last fee: start=%s, end=%s", last_fee.start, last_fee.end ) start: datetime = last_fee.end # type: ignore else: start = datetime.now() end = start + timedelta(days=( (365 / self.app.config["PAYMENT_YEARLY_FEE"]) * int(fee_form.amount.data)) ) # Store fee fee = Fee( uid=person.uid, registered_by=self.util.current_user(), amount=fee_form.amount.data, start=start, end=end, method=fee_form.method.data, ) self.storage.add(fee) self.storage.commit() logging.info( "Registered fee %d SEK for %d", fee_form.amount.data, person.uid ) # Check for user state changes if ( person.state != "active" and self.storage.session.query(Fee) .filter( Fee.uid == person.uid, Fee.start <= datetime.now(), Fee.end >= datetime.now(), ) .all() ): person.state = "active" self.storage.commit() logging.info("%s %s is now active", person.fname, person.lname) self.mail.send_mail( self.app.config["ORG_EMAIL"], "mail/board_member_active.html", member=person, ) self.mail.send_mail( person, "member_active.mail", member=person) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "Registered fee %d SEK for %s %s" % (fee_form.amount.data, person.fname, person.lname), ) ) fee_form = FeeForm(None) elif "submitPayment" in request.form and payment_form.validate(): fee_form = FeeForm(None) # Clear the other form person = ( self.storage.session.query(Person) .filter_by(uid=payment_form.uid.data) .scalar() ) if person is None: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", 'No such member "%s". Did you try the auto-complete feature?' % payment_form.uid.data, ) ) return redirect(url_for("register_fee")) # Store payment payment = Payment( uid=person.uid, registered_by=self.util.current_user(), amount=payment_form.amount.data, method=payment_form.method.data, reason=payment_form.reason.data, ) self.storage.add(payment) self.storage.commit() logging.info( "Registered payment %d SEK for %d", payment_form.amount.data, person.uid ) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "Registered payment %d SEK for %s %s" % (payment_form.amount.data, person.fname, person.lname), ) ) payment_form = PaymentForm(None) return render_template( "register_fee.html", feeForm=fee_form, paymentForm=payment_form )
def print(self) -> Union[str, Response]: form = PrintForm() if form.validate_on_submit(): # check if the post request has the file part if "file" not in request.files: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "No file part", )) return render_template("print.html", form=form) file = request.files["file"] # if user does not select file, browser also # submit an empty part without filename if file.filename == "": self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "No selected file", )) return render_template("print.html", form=form) if not file or not self.allowed_file(file.filename): self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", """That type of file is not allowed. Please try again or with a different file. Allowed files are: %s""" % (", ".join(self.app.config["ALLOWED_EXTENSIONS"])), )) return render_template("print.html", form=form) # save file to harddrive filename = "".join([ c for c in file.filename if c.isalpha() or c.isdigit() or c == ' ' ]).rstrip() filepath = os.path.join(self.app.config["UPLOAD_FOLDER"], filename + "_" + str(time.time())) file.save(filepath) # send file to printer self.cups_conn.printFile("printer1", filepath, "", {"media": "A4"}) # log that a document has been printed logging.info("Document %s printed by %s", form.file.name, self.util.current_user()) # show success message to user self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Success", "File has now been scheduled for printing.", )) form = PrintForm(None) return render_template("print.html", form=form)
def kiosk_products(self) -> Union[str, Response]: if ( "kiosk_password" not in session or session["kiosk_password"] != self.app.config["KIOSK_KEY"] ): return redirect(url_for("kiosk.kiosk_login")) if "kiosk_uid" not in session: return redirect(url_for("kiosk.kiosk_card")) person: Person = ( self.storage.session.query(Person) .filter_by(uid=session["kiosk_uid"]) .scalar() ) if not person: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Missing user %s" % (session["kiosk_uid"]), ) ) return redirect(url_for("kiosk.kiosk_card")) form = KioskProductForm() product: Product if form.validate_on_submit(): # Process form input products_input = form.products_field.data.split(",") products = [] product_amounts = [] error_processing = False # Loop through each of the received products and its respective quantity for p in products_input: product_id = float(p.split(":")[0]) product_qty = int(p.split(":")[1]) # Check if QTY is valid if product_qty < 1 or product_qty > 100: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Invalid Quantity %s" % (product_id), ) ) error_processing = True # Fetch product and add if valid product = ( self.storage.session.query(Product) .filter_by(pid=product_id) .scalar() ) if product: products.append(product) product_amounts.append(product_qty) else: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Error", "Invalid product %s" % (product_id), ) ) error_processing = True break if error_processing is not True: # Store payment for i in range(len(products)): product = products[i] for j in range(product_amounts[i]): payment = Payment( uid=person.uid, registered_by=person.uid, amount=-product.price, method="kiosk", reason="Bought %s for %.2f kr" % (product.name, product.price), ) self.storage.add(payment) self.storage.commit() logging.info( "Person %d bought %d for %.2f kr", person.uid, product.pid, product.price ) self.util.alert( KosekiAlert( KosekiAlertType.SUCCESS, "Successfully bought %s" % (product.name), "", ) ) return redirect(url_for("kiosk.kiosk_success")) return render_template( "kiosk_products.html", form=form, person=person, products=self.storage.session.query(Product) .order_by(Product.order.asc()) .all(), )
def oidc_authorization(self) -> Union[str, Response]: person: Person = self.storage.session.query(Person).filter_by( uid=self.util.current_user()).scalar() if person.state != "active": logging.warning( "User {} tried to log in via OIDC without membership!", person.uid) self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Missing membership!", "OpenID Connect login was successful, but active " + "membership is required to log in to 3rd party services.", )) return render_template("oidc.html") try: # Override claims to always only and at least return email for CF authn_req = AuthorizationRequest().from_dict({ **request.args, "claims": { "id_token": { "email": { "essential": True } } } }) logging.debug(authn_req) authn_req.verify() if authn_req["client_id"] not in self.provider.clients: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Invalid Client", "Unknown Client ID. All applications must be pre-registered.", )) return render_template("oidc.html") client = self.provider.clients[authn_req["client_id"]] if client["redirect_uri"] != authn_req["redirect_uri"]: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Incorrect Redirect URI", "Unauthorized redirect URI for this client.", )) return render_template("oidc.html") authn_response = self.provider.authorize( authn_req, str(self.util.current_user())) return_url = authn_response.request( authn_req["redirect_uri"], should_fragment_encode(authn_req)) return redirect(return_url) except Exception as err: # pylint: disable=broad-except logging.error(err) self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "OIDC Error", "Error: {}".format(str(err)), )) return render_template("oidc.html")