diff --git a/.gitignore b/.gitignore index 8ebc9f3..56268a6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ var/ *.pem *test.py trash -mongo_data_dir/* \ No newline at end of file +mongo_data_dir/* +.directory \ No newline at end of file diff --git a/internal_lib/AuthMethots.py b/internal_lib/AuthMethots.py new file mode 100644 index 0000000..95e19c8 --- /dev/null +++ b/internal_lib/AuthMethots.py @@ -0,0 +1,92 @@ +from flask import request +from flask_jwt_extended.exceptions import NoAuthorizationError +from flask_jwt_extended.utils import verify_token_claims +from flask_jwt_extended.view_decorators import _decode_jwt_from_request +from werkzeug.exceptions import Unauthorized + +from internal_lib.permission_parser import parse_permission, control_permission, is_admin +from models.User import User +from restapi.BaseAuthModel import BaseAuth + + +class AuthApi(BaseAuth): + def authorized(self): + "" + + +class AuthJWT(BaseAuth): + user = None + + def authorized(self): + try: + jwt_data, jwt_header = _decode_jwt_from_request(request_type='access') + verify_token_claims(jwt_data) + self.user = User.objects.get(id=jwt_data['identity']) + except Exception as e: + self.set_error(e) + return False + + return True + + def has_model_delete_permission(self, obj, model): + if self.user is None: + self.authorized() + + if model.__name__.lower() == "union": + return False, obj + if control_permission(self.user.user_group, model.__name__.lower(), "delete", str(obj.id), + str(self.user.union)): + return True, obj + else: + return False, obj + + def has_model_update_permission(self, obj, update: dict): + model = obj.__class__.__name__.lower() + + if self.user is None: + self.authorized() + if update.get('id') or update.get('pk'): + return False, update + if not is_admin(self.user.user_group) and update.get('union'): + return False, update + if control_permission(self.user.user_group, model, "update", str(obj.id), + str(self.user.union.id)): + return True, update + + return False, update + + def has_model_read_permission(self, qs): + from flask import current_app + if self.user is None: + self.authorized() + unions = [] + has_read = False + for right in self.user.user_group.rights: + permission = parse_permission(right) + current_app.logger.info(permission) + if permission.get('read'): + has_read = True + unions.append(permission.get('union')) + + if has_read: + if qs._collection.name == "union": + qs = qs.filter(id__in=unions, deleted=False) + else: + qs = qs.filter(union__in=unions, deleted=False) + else: + raise Unauthorized() + + return qs + + def has_model_write_permission(self, obj): + model = obj.__class__.__name__.lower() + + if self.user is None: + self.authorized() + + obj.union = self.user.union.id + if control_permission(self.user.user_group, model, "write", str(obj.id), + str(self.user.union.id)): + return True, obj + + return False, obj diff --git a/internal_lib/EncryptedField.py b/internal_lib/EncryptedField.py index 10ee028..b6efac9 100644 --- a/internal_lib/EncryptedField.py +++ b/internal_lib/EncryptedField.py @@ -18,13 +18,10 @@ class EncryptedStringField(BaseField): super().__init__(**kwargs) def __get__(self, instance, owner): - from flask import current_app as app - app.logger.error(self.name) - import binascii + import binascii if instance: value = instance._data.get(self.name) if value: - app.logger.error(value) encryptor = PKCS1_OAEP.new(self.keyPair) return encryptor.decrypt(binascii.unhexlify(value)).decode("utf-8") else: @@ -33,13 +30,6 @@ class EncryptedStringField(BaseField): def __set__(self, instance, value): super().__set__(instance, value) if value is not None: - from flask import current_app as app - app.logger.error(self.name) - app.logger.error(instance._data) - app.logger.error("data : {} ".format(value)) - print(self.name) - print(instance._data) - print(value) key = self.name try: encryptor = PKCS1_OAEP.new(self.keyPair.publickey()) @@ -58,8 +48,5 @@ class EncryptedStringField(BaseField): pass return value - def lookup_member(self, member_name): - return None - def prepare_query_value(self, op, value): return super().prepare_query_value(op, value) diff --git a/internal_lib/permission_parser.py b/internal_lib/permission_parser.py index 7e7475e..89ce3e5 100644 --- a/internal_lib/permission_parser.py +++ b/internal_lib/permission_parser.py @@ -46,39 +46,27 @@ def parse_permission(string): } -def control_permission(group, module, perm_type, itemid, unionid): +def is_admin(group): for right_string in group.rights: right = parse_permission(right_string.strip()) - print(right, right_string, group, perm_type) - if right.get('module') in ["*", module]: - return True - elif right.get('union') in ['*', unionid]: - return True - elif right.get(perm_type): - return True - elif right.get('item_id') in ['*', itemid]: - return True - elif right.get('module') in ["*", module] and right.get('union') in ['*', unionid] and right.get( - perm_type) and right.get('item_id') in ['*', itemid]: + if right.get('union') == "*": return True return False +def control_permission(group, module, perm_type, itemid, unionid): + has_perm = False + from flask import current_app -def read_permission(module, qs): - union_list = [] - for right_string in current_user.group.rights: - right = parse_permission(right_string) - if right.get('module') in [module, '*']: - if right.get('read'): - if right.get('union') != "*": - union_list.append(right.get('union')) - - if len(union_list) > 0: - if module == 'union': - qs.filter(id__in=union_list, deleted=False) - else: - qs.filter(union__in=union_list, deleted=False) - return qs + for right_string in group.rights: + right = parse_permission(right_string.strip()) + if module == "union" and perm_type == "write" and right.get("union") != "*": + has_perm = False + current_app.logger.info("short shut") + elif right.get('module') in ["*", module] and right.get('union') in ['*', unionid] and right.get( + perm_type) and right.get('item_id') in ['*', itemid]: + current_app.logger.info("long shut") + has_perm = True + return has_perm def has_permission(module, obj, reqtype, oid): diff --git a/main.py b/main.py index 886a83c..88c0145 100644 --- a/main.py +++ b/main.py @@ -1,27 +1,78 @@ -from flask import Flask +from flask import Flask, request, jsonify from flask_admin.contrib.mongoengine import ModelView +from flask_jwt_extended import JWTManager, create_access_token from mongoengine import connect +from werkzeug.security import check_password_hash +from internal_lib.AuthMethots import AuthJWT from models.Group import Group from models.Union import Union from models.User import User from restapi import MongoApi from flask_admin import Admin + """ Mongodb connection string """ -connect('adunatio', host='mongo',username="xcoder",password="4dun4710", authentication_source='admin') - +connect('adunatio', host='mongo', username="xcoder", password="4dun4710", authentication_source='admin') app = Flask(__name__) app.secret_key = "secret_key+secret_key" -api = MongoApi(app) -api.register_model(User,uri="/api/user") -api.register_model(Union,uri="/api/union") +app.config["JWT_TOKEN_LOCATION"] = "headers" +app.config["JWT_HEADER_NAME"] = "Adunation_Session_Token" +app.config["JWT_HEADER_TYPE"] = "Bearer" + +""" +flask jwt extended register +""" +jwt = JWTManager(app) + +""" +flask mongorester register +""" +api = MongoApi(app, authentication_methods=[AuthJWT]) +api.register_model(User, uri="/api/user") +api.register_model(Union, uri="/api/union") + +""" +flask admin register +""" + adm = Admin(app) adm.add_view(ModelView(User)) adm.add_view(ModelView(Union)) adm.add_view(ModelView(Group)) +""" +login function +""" + + +@app.route('/auth/login', methods=['POST']) +def login(): + if not request.is_json: + return jsonify({"message": "Missing JSON in request", "error": "parameter_error", "status":False}), 400 + + username = request.json.get('username', None) + password = request.json.get('password', None) + if not username: + return jsonify({"message": "Missing username parameter", "error": "parameter_error", "status":False}), 400 + if not password: + return jsonify({"message": "Missing password parameter", "error": "parameter_error", "status":False}), 400 + + try: + user = User.objects.get(username=username) + except Exception as e: + app.logger.error(e) + return jsonify({"message": "Bad username or password", "error": "Unauthorized", "status": False}), 401 + + if not check_password_hash(user.password,password): + return jsonify({"message": "Bad username or password", "error": "Unauthorized", "status":False}), 401 + + # Identity can be any data that is json serializable + access_token = create_access_token(identity=str(user.id)) + return jsonify(access_token=access_token,status=True), 200 + + if __name__ == '__main__': - app.run(host="0.0.0.0",port=5000,debug=True) \ No newline at end of file + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/models/Account.py b/models/Account.py index 675a4bd..1b5966f 100644 --- a/models/Account.py +++ b/models/Account.py @@ -8,3 +8,4 @@ class Account(Document): bank = StringField() number = StringField() quick_number = StringField() + deleted = BooleanField(default=False) \ No newline at end of file diff --git a/models/File.py b/models/File.py index 0c9342d..b51a605 100644 --- a/models/File.py +++ b/models/File.py @@ -7,9 +7,11 @@ class Directory(Document): path = StringField() can_see_user_group = ReferenceField(Group) is_system = BooleanField(default=False) + deleted = BooleanField(default=False) class File(Document): path = ReferenceField(Directory) file = FileField() description = StringField() + deleted = BooleanField(default=False) diff --git a/models/Group.py b/models/Group.py index 6fa0aa6..e0a2d61 100644 --- a/models/Group.py +++ b/models/Group.py @@ -7,6 +7,7 @@ class Group(Document): union = ReferenceField(Union) name = StringField() rights = ListField(StringField()) + deleted = BooleanField(default=False) def __unicode__(self): return "{} {}".format(self.union.name,self.name) @@ -14,4 +15,8 @@ class Group(Document): class PaymentGroup(Document): union = ReferenceField(Union) name = StringField() + deleted = BooleanField(default=False) discount_percent = IntField() + + def __unicode__(self): + return "{} {}".format(self.union.name, self.name) \ No newline at end of file diff --git a/models/Payment.py b/models/Payment.py index 1993bcb..cd871b7 100644 --- a/models/Payment.py +++ b/models/Payment.py @@ -17,6 +17,7 @@ class Payments(Document): regular = BooleanField(default=False) regular_type = StringField(choices=('Weekly','Monthly','Yearly')) price = DecimalField() + deleted = BooleanField(default=False) description = StringField() reference_no = StringField() file = ReferenceField(File) diff --git a/models/Union.py b/models/Union.py index 3320d8f..80be432 100644 --- a/models/Union.py +++ b/models/Union.py @@ -20,13 +20,17 @@ class Union(Document): ("-name"), ] } - name = StringField() + name = StringField(required=True) logo = ImageField(thumbnail_size=(120, 120)) description = StringField() - legal_registration_number = StringField() + legal_registration_number = StringField(required=True) headquarter = StringField() - email = StringField() + email = StringField(required=True) api_key = ListField(EmbeddedDocumentField(ApiKey)) + deleted = BooleanField(default=False) + + def __unicode__(self): + return self.name def save(self, *args, **kwargs): super(Union, self).save(*args, **kwargs) @@ -42,8 +46,9 @@ class Union(Document): user.username = "{}@root".format(self.legal_registration_number) user.user_group = group user.union = self - password = ''.join(choices(ascii_letters+digits, k=10)) - print("New union password : {}".format(password)) + from flask import current_app + password = ''.join(choices(ascii_letters + digits, k=10)) + current_app.logger.info("New union password : {}".format(password)) user.password = generate_password_hash(password) # TODO: send password via mail or sms or nothing user.save() diff --git a/models/User.py b/models/User.py index bab9400..b6da043 100644 --- a/models/User.py +++ b/models/User.py @@ -1,5 +1,6 @@ from flask_jwt_extended import current_user from mongoengine import * +from werkzeug.security import generate_password_hash from internal_lib.EncryptedField import EncryptedStringField from models.EmbededDocuments import Descriptions @@ -23,28 +24,29 @@ class User(Document): 'with_sub_docs': True, "quyery": {}, 'ignore_fields': ['password'], - 'methods': [Methods.Get, Methods.List, Methods.Create], + 'methods': [Methods.Get, Methods.List, Methods.Create, Methods.Update], "indexes": [ ('union'), ('username', 'union'), ('accept_date') ] } + deleted = BooleanField(default=False) union = ReferenceField(Union) member_no = LongField() - username = StringField() + username = StringField(required=True) photo = ImageField(thumbnail_size=(85, 120)) - password = StringField() - name = StringField() + password = StringField(required=True) + name = StringField(required=True) middle_name = StringField() - last_name = StringField() - gov_id = EncryptedStringField() + last_name = StringField(required=True) + gov_id = EncryptedStringField(required=True) mother_name = EncryptedStringField() father_name = EncryptedStringField() email = EncryptedStringField() place_of_birth = EncryptedStringField() - date_of_birth = EncryptedStringField() - telephone = EncryptedStringField() + date_of_birth = EncryptedStringField(required=True) + telephone = EncryptedStringField(required=True) job = StringField() address = EncryptedStringField() accept_date = DateTimeField() @@ -109,7 +111,7 @@ class User(Document): 'Transsexual Person', 'Transsexual Woman', 'Two-Spirit' - )) + ),required=True) custom_fields = ListField(EmbeddedDocumentField(UserCustomFields)) user_group = ReferenceField(Group) payment_group = ReferenceField(PaymentGroup) @@ -121,7 +123,10 @@ class User(Document): return str(self.id) def save(self, *args, **kwargs): - if current_user: - self.union = current_user.union + if self.union: self.member_no = User.objects.filter(union=self.union).count() + self.password = generate_password_hash(self.password) + + + super(User, self).save(*args, **kwargs) diff --git a/restapi b/restapi index b4fb234..70be57e 160000 --- a/restapi +++ b/restapi @@ -1 +1 @@ -Subproject commit b4fb234e4781092e5d8765dae527a198ab0567aa +Subproject commit 70be57ef3681f723271cc05edcbc055417a578d8