Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull request update/241129 #478

Merged
merged 1 commit into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# pylint: disable=C0103
"""user verified column

Revision ID: 76fd6db54f65
Revises: 245d4295bbd6
Create Date: 2024-11-18 09:57:42.103381

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import update, Integer, and_, Boolean
from sqlalchemy.orm import Session
from sqlalchemy.sql import table, column

# revision identifiers, used by Alembic.
revision = '76fd6db54f65'
down_revision = '245d4295bbd6'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('verified', Boolean(), nullable=False))
user_table = table(
'user',
column('deleted_at', Integer()),
column('verified', Boolean())
)
bind = op.get_bind()
session = Session(bind=bind)
try:
upd_stmt = update(user_table).values(verified=True).where(and_(
user_table.c.verified.is_(False),
user_table.c.deleted_at == 0
))
session.execute(upd_stmt)
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'verified')
# ### end Alembic commands ###
2 changes: 1 addition & 1 deletion auth/auth_server/controllers/signin.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def signin(self, **kwargs):
password=self._gen_password(),
self_registration=True, token='',
is_password_autogenerated=True)

user.verified = True
token_dict = self.token_ctl.create_token_by_user_id(
user_id=user.id, ip=ip, provider=provider,
register=register)
Expand Down
6 changes: 6 additions & 0 deletions auth/auth_server/controllers/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,14 @@ def _check_user(self, email, password=None, verification_code=None):
if password:
if user.password != hash_password(password, user.salt):
raise ForbiddenException(Err.OA0037, [])
if not user.verified:
raise ForbiddenException(Err.OA0073, [])
else:
vc_used = self.use_verification_code(email, verification_code)
if not vc_used:
raise ForbiddenException(Err.OA0071, [])
elif not user.verified:
user.verified = True
if not user.is_active:
raise ForbiddenException(Err.OA0038, [])
return user
Expand Down Expand Up @@ -94,6 +98,8 @@ def create_token_by_user_id(self, **kwargs):
raise NotFoundException(Err.OA0043, [user_id])
if not user.is_active:
raise ForbiddenException(Err.OA0038, [])
if not user.verified:
raise ForbiddenException(Err.OA0073, [])
return self.create_user_token(user, **kwargs)

def create_user_token(self, user, **kwargs):
Expand Down
8 changes: 5 additions & 3 deletions auth/auth_server/controllers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def _get_input(self, **input_):
is_password_autogenerated = input_.get(
'is_password_autogenerated', False)
self_registration = input_.get('self_registration', False)
verified = input_.get('verified', False)
if self_registration:
root_type = self.session.query(Type).filter(
and_(
Expand All @@ -48,7 +49,7 @@ def _get_input(self, **input_):
raise NotFoundException(Err.OA0064, [])
type_id = root_type.id
return (email, display_name, is_active, password, type_id, scope_id,
is_password_autogenerated)
is_password_autogenerated, verified)

@staticmethod
def _is_self_edit(user, user_to_edit_id):
Expand Down Expand Up @@ -139,7 +140,7 @@ def create(self, **kwargs):
self_registration = kwargs.pop('self_registration', False)
self.check_create_restrictions(**kwargs)
(email, display_name, is_active, password, type_id,
scope_id, is_password_autogenerated) = self._get_input(
scope_id, is_password_autogenerated, verified) = self._get_input(
self_registration=self_registration, **kwargs)
self._check_input(email, display_name, is_active, password, type_id,
scope_id)
Expand All @@ -163,7 +164,8 @@ def create(self, **kwargs):
type_id=type_id,
display_name=display_name,
is_active=is_active,
is_password_autogenerated=is_password_autogenerated
is_password_autogenerated=is_password_autogenerated,
verified=verified
)
self.session.add(user)
try:
Expand Down
3 changes: 3 additions & 0 deletions auth/auth_server/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,6 @@ class Err(enum.Enum):
OA0072 = [
"The verification code can be generated once in a minute"
]
OA0073 = [
"Email not verified"
]
5 changes: 5 additions & 0 deletions auth/auth_server/handlers/v2/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ async def post(self, **url_params):
scope_id: {type: string,
description: User scope id (None scope means root)}
token: {type: string, description: Token}
verified: {type: boolean,
description: "Is email verified?"}
responses:
201: {description: Success (returns created user data)}
400:
Expand Down Expand Up @@ -288,6 +290,9 @@ async def post(self, **url_params):
unexpected_string = ', '.join(duplicates)
raise OptHTTPError(400, Err.OA0022, [unexpected_string])
body.update(url_params)
if not self.check_cluster_secret(raises=False):
# verified users can be created only by secret
body.pop('verified', None)
self._validate_params(**body)
res = await run_task(
self.controller.create, **body, **self.token,
Expand Down
6 changes: 5 additions & 1 deletion auth/auth_server/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ class User(Base, BaseMixin):
info=ColumnPermissions.create_only)
email = Column(String(256), nullable=False, index=True,
info=ColumnPermissions.create_only)
verified = Column(Boolean, nullable=False, default=False,
info=ColumnPermissions.create_only)
password = Column(String(64), nullable=False,
info=ColumnPermissions.full)
salt = Column(String(20), nullable=False)
Expand Down Expand Up @@ -204,7 +206,8 @@ def to_json(self):
def __init__(self, email=None, type_=None, password=None,
salt=None, scope_id=None, type_id=None, display_name=None,
is_active=True, slack_connected=False,
is_password_autogenerated=False, jira_connected=False):
is_password_autogenerated=False, jira_connected=False,
verified=False):
if type_:
self.type = type_
if type_id is not None:
Expand All @@ -219,6 +222,7 @@ def __init__(self, email=None, type_=None, password=None,
self.slack_connected = slack_connected
self.is_password_autogenerated = is_password_autogenerated
self.jira_connected = jira_connected
self.verified = verified

def __repr__(self):
return f'<User {self.email}>'
Expand Down
5 changes: 5 additions & 0 deletions auth/auth_server/tests/unittests/test_api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ def setUp(self, version='v1'):
self.client = TestAuthBase.get_auth_client(version).Client(
http_provider=http_provider)
self.client.secret = secret
self.user_verified_mock = patch(
'auth.auth_server.models.models.User.verified',
new_callable=PropertyMock, return_value=True)
self.user_verified_mock.start()

def tearDown(self):
self.user_verified_mock.stop()
DBFactory.clean_type(DBType.TEST)
super().tearDown()

Expand Down
18 changes: 18 additions & 0 deletions auth/auth_server/tests/unittests/test_api_signin.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,21 @@ def test_signin_existing_user_ms(self):
token='token')
self.assertEqual(code, 201)
self.assertEqual(resp['user_email'], self.user_customer_email)

@patch.dict(os.environ, {'MICROSOFT_OAUTH_CLIENT_ID': '123'}, clear=True)
def test_signin_user_verified(self):
self.user_verified_mock.stop()
patch('auth.auth_server.controllers.user.UserController.'
'domain_blacklist').start()
token_info = (self.user_customer_email, self.user_customer_name)
with patch('auth.auth_server.controllers.signin.'
'MicrosoftOauth2Provider.verify',
return_value=token_info):
code, resp = self.client.signin(provider='microsoft',
token='token')
self.assertEqual(code, 201)

code, resp = self.client.user_get(resp['user_id'])
self.assertEqual(code, 200)
self.assertEqual(resp['email'], self.user_customer_email)
self.assertTrue(resp['verified'])
8 changes: 7 additions & 1 deletion auth/auth_server/tests/unittests/test_api_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def test_get_token_cluster_secret(self):
extract_caveats(token_info['token'])

def test_token_by_verification_code(self):
self.user_verified_mock.stop()
session = self.db_session
partner_salt = gen_salt()
partner_password = 'pass1234'
Expand Down Expand Up @@ -152,7 +153,9 @@ def test_token_by_verification_code(self):
code, resp = self.client.post('tokens', body)
self.assertEqual(code, 403)
self.assertEqual(resp['error']['error_code'], 'OA0071')

verified = session.query(User.verified).filter(
User.id == partner_user.id).scalar()
self.assertFalse(verified)
body = {
'verification_code': code_4,
'email': partner_user.email,
Expand All @@ -161,3 +164,6 @@ def test_token_by_verification_code(self):
self.assertEqual(code, 201)
self.assertEqual(resp['user_email'], partner_user.email)
self.assertTrue(vc_4.deleted_at != 0)
verified = session.query(User.verified).filter(
User.id == partner_user.id).scalar()
self.assertTrue(verified)
15 changes: 15 additions & 0 deletions auth/auth_server/tests/unittests/test_api_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,3 +857,18 @@ def test_create_email_domain_whitelist(self):
wl_email = '[email protected]'
code, response = self._create_user(email=wl_email)
self.assertEqual(code, 400)

def test_create_verified_user(self):
secret = self.client.secret
self.user_verified_mock.stop()
self.client.secret = None
code, user = self.client.user_create('[email protected]', 'pass1',
display_name='test',
verified=True)
self.assertFalse(user['verified'])

self.client.secret = secret
code, user = self.client.user_create('[email protected]', 'pass1',
display_name='test',
verified=True)
self.assertTrue(user['verified'])
Loading