def test_search(module_client, module_db, fake_auth_from_oc, fake_algolia_save, fake_algolia_search): client = module_client first_term = random_string() apikey = get_api_key(client) # Create resource and find it in the search results. resource = client.post("/api/v1/resources", json=dict( name=f"{first_term}", category="Website", url=f"{first_term}", paid=False, ), headers={'x-apikey': apikey}) result = client.get(f"/api/v1/search?q={first_term}") assert (resource.status_code == 200) assert (result.status_code == 200) assert (result.json['data'][0]['url'] == resource.json['data'].get('url')) # Update the resource and test that search results reflect changes updated_term = random_string() resource_id = resource.json['data'].get('id') resource = client.put(f"/api/v1/resources/{resource_id}", json=dict(url=f"{updated_term}", ), headers={'x-apikey': apikey}) result = client.get(f"/api/v1/search?q={updated_term}") assert (resource.status_code == 200) assert (result.status_code == 200) assert (result.json['data'][0]['url'] == resource.json['data'].get('url'))
def test_algolia_exception_error(module_client, module_db, fake_auth_from_oc, fake_algolia_exception): client = module_client first_term = random_string() apikey = get_api_key(client) result = client.get(f"/api/v1/search?q=python") assert (result.status_code == 500) resource = client.post("/api/v1/resources", json=dict( name=f"{first_term}", category="Website", url=f"{first_term}", paid=False, ), headers={'x-apikey': apikey}) assert (resource.status_code == 200) updated_term = random_string() response = client.put( f"/api/v1/resources/{resource.json['data'].get('id')}", json=dict(name="New name", languages=["New language"], category="New Category", url=f"https://{updated_term}.url", paid=False, notes="New notes"), headers={'x-apikey': apikey}) assert (response.status_code == 200)
def test_algolia_unreachable_host_error( module_client, module_db, fake_auth_from_oc, fake_algolia_unreachable_host, ): # Reset the FLASK_ENV to run Algolia error handling code environ["FLASK_ENV"] = "not_development" client = module_client first_term = random_string() apikey = get_api_key(client) response = client.get("/api/v1/search?q=python") assert (response.status_code == 500) assert ("Algolia" in response.get_json().get("errors")[0].get( "algolia-failed").get("message")) response = client.post("/api/v1/resources", json=[ dict( name=f"{first_term}", category="Website", url=f"{first_term}", paid=False, ) ], headers={'x-apikey': apikey}) assert (response.status_code == 500) assert ("Algolia" in response.get_json().get("errors")[0].get( "algolia-failed").get("message")) updated_term = random_string() response = client.put("/api/v1/resources/1", json=dict(name="New name", languages=["New language"], category="New Category", url=f"https://{updated_term}.url", paid=False, notes="New notes"), headers={'x-apikey': apikey}) assert (response.status_code == 500) assert ("Algolia" in response.get_json().get("errors")[0].get( "algolia-failed").get("message")) # Set it back to development for other tests environ["FLASK_ENV"] = "development"
def update_user_info(): """ Input - profile_picture (optional): base64 of the profile picture. Set to null to remove the profile picture - name (optional) """ user = g.user data = request.get_json() or {} if "profile_picture" in data: if data["profile_picture"] is None: if user.profile_picture_id: file = user.profile_picture user.profile_picture_id = None Session.flush() if file: File.delete(file.id) s3.delete(file.path) Session.flush() else: raw_data = base64.decodebytes(data["profile_picture"].encode()) file_path = random_string(30) file = File.create(user_id=user.id, path=file_path) Session.flush() s3.upload_from_bytesio(file_path, BytesIO(raw_data)) user.profile_picture_id = file.id Session.flush() if "name" in data: user.name = data["name"] Session.commit() return jsonify(user_to_dict(user))
def generate_key(): print(random_string( min_size=32, max_size=128, punctuation=True, exposant=6 ))
def paddle_coupon(): LOG.d(f"paddle coupon callback %s", request.form) if not paddle_utils.verify_incoming_request(dict(request.form)): LOG.e("request not coming from paddle. Request data:%s", dict(request.form)) return "KO", 400 product_id = request.form.get("p_product_id") if product_id != PADDLE_COUPON_ID: LOG.e("product_id %s not match with %s", product_id, PADDLE_COUPON_ID) return "KO", 400 email = request.form.get("email") LOG.d("Paddle coupon request for %s", email) coupon = Coupon.create( code=random_string(30), comment="For 1-year coupon", expires_date=arrow.now().shift(years=1, days=-1), commit=True, ) return ( f"Your 1-year coupon is <b>{coupon.code}</b> <br> " f"It's valid until <b>{coupon.expires_date.date().isoformat()}</b>" )
def copy_student_name(apps, schema_editor): db_alias = schema_editor.connection.alias emit_post_migrate_signal(2, False, db_alias) Permission = apps.get_model('auth', 'Permission') Group = apps.get_model('auth', 'Group') global group_and_permission for group_name, permission_list in group_and_permission: new_group, group_create = Group.objects.get_or_create(name=group_name) new_group.save() for permission_name in permission_list: permission, _ = Permission.objects.get_or_create( name=permission_name) permission.save() new_group.permissions.add(permission) Parent = apps.get_model('app', 'Parent') User = apps.get_model('auth', 'User') Profile = apps.get_model('app', 'Profile') Student = apps.get_model('app', 'Student') parents = Parent.objects.all() for parent in parents: username = random_string()[:30] salt = random_string()[:5] password = "******" user = User(username=username) user.email = "" user.password = make_password(password, salt) user.save() student_group = Group.objects.get(name="学生") user.groups.add(student_group) profile = Profile(user=user, phone="fake" + random_string()[:16]) profile.save() student = Student(name=parent.old_student_name, school_name=parent.old_student_name, user=user) student.save() parent.students.add(student)
def generate_access_token() -> str: """generate an access-token that does not exist before""" access_token = random_string(40) if not OauthToken.get_by(access_token=access_token): return access_token # Rerun the function LOG.warning("access token already exists, generate a new one") return generate_access_token()
def create_file_from_url(user, url) -> File: file_path = random_string(30) file = File.create(path=file_path, user_id=user.id) s3.upload_from_url(url, file_path) db.session.flush() LOG.d("upload file %s to s3", file) return file
def false_validation(module_client, module_db, fake_auth_from_oc, fake_algolia_save, fake_validate_resource): # Given the validate_resource method fails to catch errors # When we commit the resource to the database # Then the api returns a 422 response client = module_client first_term = random_string() apikey = get_api_key(client) resource = client.post("/api/v1/resources", json=dict( name=f"{first_term}", category="Website", url=f"{first_term}", paid=False, ), headers={'x-apikey': apikey} ) assert (resource.status_code == 200) id = resource.json['data'].get("id") resource = client.post("/api/v1/resources", json=dict( name=f"{first_term}", category="Website", url=f"{first_term}", paid=False, ), headers={'x-apikey': apikey} ) assert (resource.status_code == 422) assert (resource.json['data'].get("errors") is not None) assert (resource.json['data'].get("errors")['-'.join(err_map.get(resource.status_code).split(' ')).lower()] == msg_map[resource.status_code]) resource = client.put(f"/api/v1/resources/{id}", json=dict( name="New name", languages=["New language"], category="New Category", url=f"{first_term}", paid=False, notes="New notes" ), headers={'x-apikey': apikey} ) assert (resource.status_code == 422) assert (resource.json['data'].get("errors") is not None) assert (resource.json['data'].get("errors")['-'.join(err_map.get(resource.status_code).split(' ')).lower()] == msg_map[resource.status_code])
def create(cls, user_id, name): # generate unique code found = False while not found: code = random_string(60) if not cls.get_by(code=code): found = True a = cls(user_id=user_id, code=code, name=name) db.session.add(a) return a
def send_reset_password_email(user): """ generate a new ResetPasswordCode and send it over email to user """ # the activation code is valid for 1h reset_password_code = ResetPasswordCode.create(user_id=user.id, code=random_string(60)) db.session.commit() reset_password_link = f"{URL}/auth/reset_password?code={reset_password_code.code}" email_utils.send_reset_password_email(user.email, reset_password_link)
def create_new(cls, name, user_id) -> "Client": # generate a client-id oauth_client_id = generate_oauth_client_id(name) oauth_client_secret = random_string(40) client = Client.create( name=name, oauth_client_id=oauth_client_id, oauth_client_secret=oauth_client_secret, user_id=user_id, ) return client
def send_activation_email(user, next_url): # the activation code is valid for 1h activation = ActivationCode.create(user_id=user.id, code=random_string(30)) db.session.commit() # Send user activation email activation_link = f"{URL}/auth/activate?code={activation.code}" if next_url: LOG.d("redirect user to %s after activation", next_url) activation_link = activation_link + "&next=" + encode_url(next_url) email_utils.send_activation_email(user.email, user.name, activation_link)
def generate_oauth_client_id(client_name) -> str: oauth_client_id = convert_to_id(client_name) + "-" + random_string() # check that the client does not exist yet if not Client.get_by(oauth_client_id=oauth_client_id): LOG.debug("generate oauth_client_id %s", oauth_client_id) return oauth_client_id # Rerun the function LOG.warning("client_id %s already exists, generate a new client_id", oauth_client_id) return generate_oauth_client_id(client_name)
def api_create_drop(): """API view function to create a new drop.""" if request.form.get('data') == None: return jsonify( {"error": "You must provide some data when creating a new drop."}), 400 drop = models.Drop() title = request.form.get('title') if title != None and title != '': drop.title = title drop.created_at = datetime.datetime.utcnow() drop.data = request.form.get('data') publicly_listed = request.form.get('publicly_listed', app.config['DATADROP_DEFAULT_LISTED']) drop.publicly_listed = publicly_listed expires = request.form.get('expires', app.config['DATADROP_DEFAULT_EXPIRES']) drop.expires = expires expires_in_string = request.form.get( 'expires_in', app.config['DATADROP_DEFAULT_EXPIRES_IN']) # set the parsed expire time (in seconds); if it's invalid, the default is used drop.expires_in = pytimeparse.parse(expires_in_string) self_destructs = request.form.get( 'self_destructs', app.config['DATADROP_DEFAULT_SELF_DESTRUCTS']) drop.self_destructs = self_destructs self_destructs_in = int( request.form.get('self_destructs_in', app.config['DATADROP_DEFAULT_SELF_DESTRUCTS_IN'])) drop.self_destructs_in = self_destructs_in drop_key_strings = request.form.getlist('drop_keys') if drop_key_strings != None and drop_key_strings != []: # for each drop key that was provided, check if it exists in the database; # if it doesn't, create it drop_keys = [] # a list of key instances for k in drop_key_strings: potential_key = db.DropKey.filter_by(key=k.lower()).one_or_none() if potential_key == None: potential_key = db.save(models.DropKey(key=k.lower())) drop_keys.append(potential_key) drop.drop_keys = drop_keys # to avoid unique urlstring errors made_urlstring = False while made_urlstring == False: u = utils.random_string(app.config['DATADROP_URLSTRING_LENGTH']) if db.Drop.filter_by(urlstring=u).one_or_none() == None: made_urlstring = True drop.urlstring = u db.save(drop) # now build the response r = get_drop_dict(drop, include_drop_keys=True, just_created=True) return jsonify(r), 201
def handle(self, *args, **options): for traider_number in range(0, TRAIDERS_COUNT): log.debug('Traider {}'.format(traider_number)) # create trader trader, _ = Trader.objects.get_or_create( name=utils.random_string(10), ) # create trader deals deals = [] deal_amounts = [] for deal_number in range(0, TRAIDER_DEALS_COUNT): deal_amount = utils.random_decimal() deal_result_amount = deal_amount + utils.random_decimal() deal = Deal(trader=trader, amount=deal_amount, result_amount=deal_result_amount, created_at=utils.random_date()) deals.append(deal) deal_amounts.append(deal_result_amount) # create trader transactions transactions = [] transaction_amounts = [] for deal_number in range(0, TRAIDER_TRANSACTIONS_COUNT): _transaction = Transaction( trader=trader, amount=utils.random_positive_decimal(), type=random.choice([ Transaction.TRANSACTION_TYPE_DEPOSIT, Transaction.TRANSACTION_TYPE_WITHDRAWAL, ]), created_at=utils.random_date(), ) transactions.append(_transaction) if _transaction.is_type_deposit(): transaction_amounts.append(_transaction.amount) else: transaction_amounts.append(_transaction.amount * (-1)) Deal.objects.bulk_create(deals) Transaction.objects.bulk_create(transactions) # update trader balance by deal_amounts and transaction_amounts trader.balance = sum(deal_amounts) + sum(transaction_amounts) trader.save(update_fields=['balance'])
def create(cls, email, name, password=None, **kwargs): user: User = super(User, cls).create(email=email, name=name, **kwargs) if not password: # set a random password password = random_string(20) user.set_password(password) db.session.flush() # create a first alias mail to show user how to use when they login GenEmail.create_new(user.id, prefix="my-first-alias") db.session.flush() return user
def handle_sender_email(envelope: Envelope): filename = (arrow.now().format("YYYY-MM-DD_HH-mm-ss") + "_" + random_string(10) + ".eml") filepath = os.path.join(SENDER_DIR, filename) with open(filepath, "wb") as f: f.write(envelope.original_content) LOG.d("Write email to sender at %s", filepath) msg = email.message_from_bytes(envelope.original_content) orig = get_orig_message_from_bounce(msg) if orig: LOG.warning("Original message %s -> %s saved at %s", orig["From"], orig["To"], filepath) return "250 email to sender accepted"
def construct_redirect_args(client, client_user, nonce, redirect_uri, response_types, scope, state) -> dict: redirect_args = {} if state: redirect_args["state"] = state else: LOG.w("more security reason, state should be added. client %s", client) if scope: redirect_args["scope"] = scope auth_code = None if ResponseType.CODE in response_types: auth_code = AuthorizationCode.create( client_id=client.id, user_id=current_user.id, code=random_string(), scope=scope, redirect_uri=redirect_uri, response_type=response_types_to_str(response_types), nonce=nonce, ) redirect_args["code"] = auth_code.code oauth_token = None if ResponseType.TOKEN in response_types: # create access-token oauth_token = OauthToken.create( client_id=client.id, user_id=current_user.id, scope=scope, redirect_uri=redirect_uri, access_token=generate_access_token(), response_type=response_types_to_str(response_types), ) Session.add(oauth_token) redirect_args["access_token"] = oauth_token.access_token if ResponseType.ID_TOKEN in response_types: redirect_args["id_token"] = make_id_token( client_user, nonce, oauth_token.access_token if oauth_token else None, auth_code.code if auth_code else None, ) Session.commit() return redirect_args
def batch_import_route(): # only for users who have custom domains if not current_user.verified_custom_domains(): flash("Alias batch import is only available for custom domains", "warning") if current_user.disable_import: flash( "you cannot use the import feature, please contact SimpleLogin team", "error", ) return redirect(url_for("dashboard.index")) batch_imports = BatchImport.filter_by(user_id=current_user.id).all() if request.method == "POST": alias_file = request.files["alias-file"] file_path = random_string(20) + ".csv" file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio(file_path, alias_file) Session.flush() LOG.d("upload file %s to s3 at %s", file, file_path) bi = BatchImport.create(user_id=current_user.id, file_id=file.id) Session.flush() LOG.d("Add a batch import job %s for %s", bi, current_user) # Schedule batch import job Job.create( name=JOB_BATCH_IMPORT, payload={"batch_import_id": bi.id}, run_at=arrow.now(), ) Session.commit() flash( "The file has been uploaded successfully and the import will start shortly", "success", ) return redirect(url_for("dashboard.batch_import_route")) return render_template("dashboard/batch_import.html", batch_imports=batch_imports)
def client_detail(client_id): form = EditClientForm() is_new = "is_new" in request.args client = Client.get(client_id) if not client: flash("no such client", "warning") return redirect(url_for("developer.index")) if client.user_id != current_user.id: flash("you cannot see this app", "warning") return redirect(url_for("developer.index")) if form.validate_on_submit(): client.name = form.name.data client.home_url = form.home_url.data if form.icon.data: # todo: remove current icon if any # todo: handle remove icon file_path = random_string(30) file = File.create(path=file_path) s3.upload_from_bytesio(file_path, BytesIO(form.icon.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) client.icon_id = file.id db.session.flush() db.session.commit() flash(f"{client.name} has been updated", "success") return redirect(url_for("developer.client_detail", client_id=client.id)) return render_template( "developer/client_details/basic_info.html", form=form, client=client, is_new=is_new, )
def send_reset_password_email(user): """ generate a new ResetPasswordCode and send it over email to user """ # the activation code is valid for 1h reset_password_code = ResetPasswordCode.create(user_id=user.id, code=random_string(60)) db.session.commit() reset_password_link = f"{URL}/auth/reset_password?code={reset_password_code.code}" email_utils.send_reset_password_email(user.email, user.name, reset_password_link) flash( "You are going to receive an email containing instruction to change your password", "success", )
def cut(): print(request.form.get("longUrl")) longUrl = utils.convert(str(request.form.get("longUrl"))) req = u.query.filter(u.longUrl == longUrl).first() print(req) if (req != None): shortUrl = req.shortUrl else: shortUrl = utils.random_string(size=4) db_session.add(Urls(shortUrl, longUrl)) db_session.commit() response = {"status": True, "shortUrl": shortUrl, "longUrl": longUrl} print(longUrl) try: requests.get(response["longUrl"]) except: response["status"] = False return render_template('cut.html', response=response)
def post(self): """Endpoint to reset a user password""" data = request.get_json() email = data.get('email') user_data = dict(email=email) if check_missing_field(**user_data): return jsonify(check_missing_field(**user_data)), 422 if check_email(email): return check_email(email) email = normalise_email(email) user = User.query.filter_by(email=email).first() if not user: return self.generate_response(messages['valid_email'], 401) password = random_string() hash_password = Bcrypt().generate_password_hash(password).decode() send_reset_password(email, password) user.update(user, password=hash_password) return self.generate_response(messages['sent_mail'], 201)
def reset_password1(): form = Reset_pwd1() if form.validate_on_submit(): global authcode authcode = random_string(length=6) user = User.query.filter( or_(User.username == form.username.data, User.email == form.username.data)).first() if user: global uname uname = user.username send_mail([user.email], '验证码', 'users/mail_auth', username=user.username, authcode=authcode) flash('验证码已经发送') return redirect(url_for('users.reset_password2', )) else: flash('没有此用户!') return render_template('users/reset_pwd1.html', form=form)
def create(cls, email, name, password=None, **kwargs): user: User = super(User, cls).create(email=email, name=name, **kwargs) if not password: # set a random password password = random_string(20) user.set_password(password) db.session.flush() # create a first alias mail to show user how to use when they login GenEmail.create_new(user.id, prefix="my-first-alias") db.session.flush() # Schedule onboarding emails Job.create( name=JOB_ONBOARDING_1, payload={"user_id": user.id}, run_at=arrow.now().shift(days=1), ) db.session.flush() return user
def setting(): form = SettingForm() promo_form = PromoCodeForm() change_email_form = ChangeEmailForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-email": if change_email_form.validate(): # whether user can proceed with the email update new_email_valid = True if (sanitize_email(change_email_form.email.data) != current_user.email and not pending_email): new_email = sanitize_email(change_email_form.email.data) # check if this email is not already used if personal_email_already_used(new_email) or Alias.get_by( email=new_email): flash(f"Email {new_email} already used", "error") new_email_valid = False elif not email_can_be_used_as_mailbox(new_email): flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False # a pending email change with the same email exists from another user elif EmailChange.get_by(new_email=new_email): other_email_change: EmailChange = EmailChange.get_by( new_email=new_email) LOG.warning( "Another user has a pending %s with the same email address. Current user:%s", other_email_change, current_user, ) if other_email_change.is_expired(): LOG.d("delete the expired email change %s", other_email_change) EmailChange.delete(other_email_change.id) db.session.commit() else: flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False if new_email_valid: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) return redirect(url_for("dashboard.setting")) if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash("Your profile has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-password": flash( "You are going to receive an email containing instructions to change your password", "success", ) send_reset_password_email(current_user) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "delete-account": # Schedule delete account job LOG.warning("schedule delete account job for %s", current_user) Job.create( name=JOB_DELETE_ACCOUNT, payload={"user_id": current_user.id}, run_at=arrow.now(), commit=True, ) flash( "Your account deletion has been scheduled. " "You'll receive an email when the deletion is finished", "success", ) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get( "form-name") == "change-random-alias-default-domain": default_domain = request.form.get("random-alias-default-domain") if default_domain: sl_domain: SLDomain = SLDomain.get_by(domain=default_domain) if sl_domain: if sl_domain.premium_only and not current_user.is_premium( ): flash("You cannot use this domain", "error") return redirect(url_for("dashboard.setting")) current_user.default_alias_public_domain_id = sl_domain.id current_user.default_alias_custom_domain_id = None else: custom_domain = CustomDomain.get_by(domain=default_domain) if custom_domain: # sanity check if (custom_domain.user_id != current_user.id or not custom_domain.verified): LOG.exception("%s cannot use domain %s", current_user, default_domain) else: current_user.default_alias_custom_domain_id = ( custom_domain.id) current_user.default_alias_public_domain_id = None else: current_user.default_alias_custom_domain_id = None current_user.default_alias_public_domain_id = None db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-sender-format": sender_format = int(request.form.get("sender-format")) if SenderFormatEnum.has_value(sender_format): current_user.sender_format = sender_format current_user.sender_format_updated_at = arrow.now() db.session.commit() flash("Your sender format preference has been updated", "success") db.session.commit() return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "replace-ra": choose = request.form.get("replace-ra") if choose == "on": current_user.replace_reverse_alias = True else: current_user.replace_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "sender-in-ra": choose = request.form.get("enable") if choose == "on": current_user.include_sender_in_reverse_alias = True else: current_user.include_sender_in_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "export-data": return redirect(url_for("api.export_data")) elif request.form.get("form-name") == "export-alias": return redirect(url_for("api.export_aliases")) manual_sub = ManualSubscription.get_by(user_id=current_user.id) apple_sub = AppleSubscription.get_by(user_id=current_user.id) coinbase_sub = CoinbaseSubscription.get_by(user_id=current_user.id) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, SenderFormatEnum=SenderFormatEnum, promo_form=promo_form, change_email_form=change_email_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, manual_sub=manual_sub, apple_sub=apple_sub, coinbase_sub=coinbase_sub, FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN, )
def setting(): form = SettingForm() promo_form = PromoCodeForm() change_email_form = ChangeEmailForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-email": if change_email_form.validate(): # whether user can proceed with the email update new_email_valid = True if (change_email_form.email.data.lower().strip() != current_user.email and not pending_email): new_email = change_email_form.email.data.strip().lower() # check if this email is not already used if personal_email_already_used(new_email) or Alias.get_by( email=new_email): flash(f"Email {new_email} already used", "error") new_email_valid = False elif not email_can_be_used_as_mailbox(new_email): flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False # a pending email change with the same email exists from another user elif EmailChange.get_by(new_email=new_email): other_email_change: EmailChange = EmailChange.get_by( new_email=new_email) LOG.warning( "Another user has a pending %s with the same email address. Current user:%s", other_email_change, current_user, ) if other_email_change.is_expired(): LOG.d("delete the expired email change %s", other_email_change) EmailChange.delete(other_email_change.id) db.session.commit() else: flash( "You cannot use this email address as your personal inbox.", "error", ) new_email_valid = False if new_email_valid: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) return redirect(url_for("dashboard.setting")) if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(user_id=current_user.id, path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash(f"Your profile has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-password": flash( "You are going to receive an email containing instructions to change your password", "success", ) send_reset_password_email(current_user) return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "delete-account": LOG.warning("Delete account %s", current_user) User.delete(current_user.id) db.session.commit() flash("Your account has been deleted", "success") logout_user() return redirect(url_for("auth.register")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get( "form-name") == "change-random-alias-default-domain": default_domain = request.form.get("random-alias-default-domain") if default_domain: sl_domain: SLDomain = SLDomain.get_by(domain=default_domain) if sl_domain: if sl_domain.premium_only and not current_user.is_premium( ): flash("You cannot use this domain", "error") return redirect(url_for("dashboard.setting")) # make sure only default_random_alias_domain_id or default_random_alias_public_domain_id is set current_user.default_random_alias_public_domain_id = sl_domain.id current_user.default_random_alias_domain_id = None else: custom_domain = CustomDomain.get_by(domain=default_domain) if custom_domain: # sanity check if (custom_domain.user_id != current_user.id or not custom_domain.verified): LOG.exception("%s cannot use domain %s", current_user, default_domain) else: # make sure only default_random_alias_domain_id or # default_random_alias_public_domain_id is set current_user.default_random_alias_domain_id = ( custom_domain.id) current_user.default_random_alias_public_domain_id = None else: current_user.default_random_alias_domain_id = None current_user.default_random_alias_public_domain_id = None db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "change-sender-format": sender_format = int(request.form.get("sender-format")) if SenderFormatEnum.has_value(sender_format): current_user.sender_format = sender_format db.session.commit() flash("Your sender format preference has been updated", "success") db.session.commit() return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "replace-ra": choose = request.form.get("replace-ra") if choose == "on": current_user.replace_reverse_alias = True else: current_user.replace_reverse_alias = False db.session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "export-data": data = { "email": current_user.email, "name": current_user.name, "aliases": [], "apps": [], "custom_domains": [], } for alias in Alias.filter_by( user_id=current_user.id).all(): # type: Alias data["aliases"].append( dict(email=alias.email, enabled=alias.enabled)) for custom_domain in CustomDomain.filter_by( user_id=current_user.id).all(): data["custom_domains"].append(custom_domain.domain) for app in Client.filter_by( user_id=current_user.id): # type: Client data["apps"].append( dict(name=app.name, home_url=app.home_url, published=app.published)) return Response( json.dumps(data), mimetype="text/json", headers={ "Content-Disposition": "attachment;filename=data.json" }, ) elif request.form.get("form-name") == "export-alias": data = [["alias", "note", "enabled"]] for alias in Alias.filter_by( user_id=current_user.id).all(): # type: Alias data.append([alias.email, alias.note, alias.enabled]) si = StringIO() cw = csv.writer(si) cw.writerows(data) output = make_response(si.getvalue()) output.headers[ "Content-Disposition"] = "attachment; filename=aliases.csv" output.headers["Content-type"] = "text/csv" return output manual_sub = ManualSubscription.get_by(user_id=current_user.id) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, SenderFormatEnum=SenderFormatEnum, promo_form=promo_form, change_email_form=change_email_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, manual_sub=manual_sub, FIRST_ALIAS_DOMAIN=FIRST_ALIAS_DOMAIN, )
def setting(): form = SettingForm() promo_form = PromoCodeForm() email_change = EmailChange.get_by(user_id=current_user.id) if email_change: pending_email = email_change.new_email else: pending_email = None if request.method == "POST": if request.form.get("form-name") == "update-profile": if form.validate(): profile_updated = False # update user info if form.name.data != current_user.name: current_user.name = form.name.data db.session.commit() profile_updated = True if form.profile_picture.data: file_path = random_string(30) file = File.create(path=file_path) s3.upload_from_bytesio( file_path, BytesIO(form.profile_picture.data.read())) db.session.flush() LOG.d("upload file %s to s3", file) current_user.profile_picture_id = file.id db.session.commit() profile_updated = True if profile_updated: flash(f"Your profile has been updated", "success") if (form.email.data and form.email.data != current_user.email and not pending_email): new_email = form.email.data # check if this email is not used by other user, or as alias if (User.get_by(email=new_email) or GenEmail.get_by(email=new_email) or DeletedAlias.get_by(email=new_email)): flash(f"Email {new_email} already used", "error") elif new_email.endswith(EMAIL_DOMAIN): flash( "You cannot use alias as your personal inbox. Nice try though 😉", "error", ) else: email_change = EmailChange.create( user_id=current_user.id, code=random_string( 60), # todo: make sure the code is unique new_email=new_email, ) db.session.commit() send_change_email_confirmation(current_user, email_change) flash( "A confirmation email is on the way, please check your inbox", "success", ) elif request.form.get("form-name") == "change-password": send_reset_password_email(current_user) elif request.form.get("form-name") == "notification-preference": choose = request.form.get("notification") if choose == "on": current_user.notification = True else: current_user.notification = False db.session.commit() flash("Your notification preference has been updated", "success") elif request.form.get("form-name") == "delete-account": User.delete(current_user.id) db.session.commit() flash("Your account has been deleted", "success") logout_user() return redirect(url_for("auth.register")) elif request.form.get("form-name") == "change-alias-generator": scheme = int(request.form.get("alias-generator-scheme")) if AliasGeneratorEnum.has_value(scheme): current_user.alias_generator = scheme db.session.commit() flash("Your preference has been updated", "success") elif request.form.get("form-name") == "export-data": data = { "email": current_user.email, "name": current_user.name, "aliases": [], "apps": [], "custom_domains": [], } for alias in GenEmail.filter_by( user_id=current_user.id).all(): # type: GenEmail data["aliases"].append( dict(email=alias.email, enabled=alias.enabled)) for custom_domain in CustomDomain.filter_by( user_id=current_user.id).all(): data["custom_domains"].append(custom_domain.domain) for app in Client.filter_by( user_id=current_user.id): # type: Client data["apps"].append( dict(name=app.name, home_url=app.home_url, published=app.published)) return Response( json.dumps(data), mimetype="text/json", headers={ "Content-Disposition": "attachment;filename=data.json" }, ) return redirect(url_for("dashboard.setting")) return render_template( "dashboard/setting.html", form=form, PlanEnum=PlanEnum, promo_form=promo_form, pending_email=pending_email, AliasGeneratorEnum=AliasGeneratorEnum, )
def authorize(): """ Redirected from client when user clicks on "Login with Server". This is a GET request with the following field in url - client_id - (optional) state - response_type: must be code """ oauth_client_id = request.args.get("client_id") state = request.args.get("state") scope = request.args.get("scope") redirect_uri = request.args.get("redirect_uri") response_mode = request.args.get("response_mode") nonce = request.args.get("nonce") try: response_types: [ResponseType] = get_response_types(request) except ValueError: return ( "response_type must be code, token, id_token or certain combination of these." " Please see /.well-known/openid-configuration to see what response_type are supported ", 400, ) if set(response_types) not in SUPPORTED_OPENID_FLOWS: return ( f"SimpleLogin only support the following OIDC flows: {SUPPORTED_OPENID_FLOWS_STR}", 400, ) if not redirect_uri: LOG.d("no redirect uri") return "redirect_uri must be set", 400 client = Client.get_by(oauth_client_id=oauth_client_id) if not client: final_redirect_uri = ( f"{redirect_uri}?error=invalid_client_id&client_id={oauth_client_id}" ) return redirect(final_redirect_uri) # check if redirect_uri is valid # allow localhost by default hostname, scheme = get_host_name_and_scheme(redirect_uri) if hostname != "localhost" and hostname != "127.0.0.1": # support custom scheme for mobile app if scheme == "http": final_redirect_uri = f"{redirect_uri}?error=http_not_allowed" return redirect(final_redirect_uri) if not RedirectUri.get_by(client_id=client.id, uri=redirect_uri): final_redirect_uri = f"{redirect_uri}?error=unknown_redirect_uri" return redirect(final_redirect_uri) # redirect from client website if request.method == "GET": if current_user.is_authenticated: suggested_email, other_emails, email_suffix = None, [], None suggested_name, other_names = None, [] # user has already allowed this client client_user: ClientUser = ClientUser.get_by( client_id=client.id, user_id=current_user.id) user_info = {} if client_user: LOG.debug("user %s has already allowed client %s", current_user, client) user_info = client_user.get_user_info() else: suggested_email, other_emails = current_user.suggested_emails( client.name) suggested_name, other_names = current_user.suggested_names() user_custom_domains = [ cd.domain for cd in current_user.verified_custom_domains() ] # List of (is_custom_domain, alias-suffix, time-signed alias-suffix) suffixes = available_suffixes(current_user) return render_template( "oauth/authorize.html", Scope=Scope, EMAIL_DOMAIN=EMAIL_DOMAIN, **locals(), ) else: # after user logs in, redirect user back to this page return render_template( "oauth/authorize_nonlogin_user.html", client=client, next=request.url, Scope=Scope, ) else: # POST - user allows or denies if request.form.get("button") == "deny": LOG.debug("User %s denies Client %s", current_user, client) final_redirect_uri = f"{redirect_uri}?error=deny&state={state}" return redirect(final_redirect_uri) LOG.debug("User %s allows Client %s", current_user, client) client_user = ClientUser.get_by(client_id=client.id, user_id=current_user.id) # user has already allowed this client, user cannot change information if client_user: LOG.d("user %s has already allowed client %s", current_user, client) else: alias_prefix = request.form.get("prefix") signed_suffix = request.form.get("suffix") alias = None # user creates a new alias, not using suggested alias if alias_prefix: # should never happen as this is checked on the front-end if not current_user.can_create_new_alias(): raise Exception( f"User {current_user} cannot create custom email") # hypothesis: user will click on the button in the 600 secs try: alias_suffix = signer.unsign(signed_suffix, max_age=600).decode() except SignatureExpired: LOG.warning("Alias creation time expired for %s", current_user) flash("Alias creation time is expired, please retry", "warning") return redirect(request.url) except Exception: LOG.exception("Alias suffix is tampered, user %s", current_user) flash("Unknown error, refresh the page", "error") return redirect(request.url) user_custom_domains = [ cd.domain for cd in current_user.verified_custom_domains() ] from app.dashboard.views.custom_alias import verify_prefix_suffix if verify_prefix_suffix(current_user, alias_prefix, alias_suffix): full_alias = alias_prefix + alias_suffix if (Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias) or DomainDeletedAlias.get_by(email=full_alias)): LOG.exception("alias %s already used, very rare!", full_alias) flash(f"Alias {full_alias} already used", "error") return redirect(request.url) else: alias = Alias.create( user_id=current_user.id, email=full_alias, mailbox_id=current_user.default_mailbox_id, ) # get the custom_domain_id if alias is created with a custom domain if alias_suffix.startswith("@"): alias_domain = alias_suffix[1:] domain = CustomDomain.get_by(domain=alias_domain) if domain: alias.custom_domain_id = domain.id db.session.flush() flash(f"Alias {full_alias} has been created", "success") # only happen if the request has been "hacked" else: flash("something went wrong", "warning") return redirect(request.url) # User chooses one of the suggestions else: chosen_email = request.form.get("suggested-email") # todo: add some checks on chosen_email if chosen_email != current_user.email: alias = Alias.get_by(email=chosen_email) if not alias: alias = Alias.create( email=chosen_email, user_id=current_user.id, mailbox_id=current_user.default_mailbox_id, ) db.session.flush() suggested_name = request.form.get("suggested-name") custom_name = request.form.get("custom-name") use_default_avatar = request.form.get("avatar-choice") == "default" client_user = ClientUser.create(client_id=client.id, user_id=current_user.id) if alias: client_user.alias_id = alias.id if custom_name: client_user.name = custom_name elif suggested_name != current_user.name: client_user.name = suggested_name if use_default_avatar: # use default avatar LOG.d("use default avatar for user %s client %s", current_user, client) client_user.default_avatar = True db.session.flush() LOG.d("create client-user for client %s, user %s", client, current_user) redirect_args = {} if state: redirect_args["state"] = state else: LOG.warning( "more security reason, state should be added. client %s", client) if scope: redirect_args["scope"] = scope auth_code = None if ResponseType.CODE in response_types: # Create authorization code auth_code = AuthorizationCode.create( client_id=client.id, user_id=current_user.id, code=random_string(), scope=scope, redirect_uri=redirect_uri, response_type=response_types_to_str(response_types), ) db.session.add(auth_code) redirect_args["code"] = auth_code.code oauth_token = None if ResponseType.TOKEN in response_types: # create access-token oauth_token = OauthToken.create( client_id=client.id, user_id=current_user.id, scope=scope, redirect_uri=redirect_uri, access_token=generate_access_token(), response_type=response_types_to_str(response_types), ) db.session.add(oauth_token) redirect_args["access_token"] = oauth_token.access_token if ResponseType.ID_TOKEN in response_types: redirect_args["id_token"] = make_id_token( client_user, nonce, oauth_token.access_token if oauth_token else None, auth_code.code if auth_code else None, ) db.session.commit() # should all params appended the url using fragment (#) or query fragment = False if response_mode and response_mode == "fragment": fragment = True # if response_types contain "token" => implicit flow => should use fragment # except if client sets explicitly response_mode if not response_mode: if ResponseType.TOKEN in response_types: fragment = True # construct redirect_uri with redirect_args return redirect(construct_url(redirect_uri, redirect_args, fragment))