Esempio n. 1
0
    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")
Esempio n. 2
0
    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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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,
        )
Esempio n. 5
0
    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(),
        )
Esempio n. 6
0
    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)
Esempio n. 7
0
    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)
Esempio n. 8
0
    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)
Esempio n. 9
0
 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,
     )
Esempio n. 10
0
 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,
     )
Esempio n. 11
0
 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,
     )
Esempio n. 12
0
    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)
Esempio n. 13
0
    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)
Esempio n. 14
0
    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(),
        )
Esempio n. 15
0
    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
        )
Esempio n. 16
0
    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)
Esempio n. 17
0
    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(),
        )
Esempio n. 18
0
    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")