Exemple #1
0
class UserSecurity(object):
    _model = User

    _share = False

    def _verify_password(self, mobile_or_token, password):
        if request.method == 'OPTIONS':
            token = request.values.get('token', False)
            if not token:
                return True

        token = request.values.get('token', None)
        if (not token) and mobile_or_token and (not password):
            token = mobile_or_token
        if token:
            user = get_user_from_auth_token(token, self._model)
            if user and user.active:
                g.current_user = user
                g.token_used = True
                return True

        if mobile_or_token and password:
            user = None
            if hasattr(self._model, 'mobile') and hasattr(
                    self._model, 'email'):
                user = self._model.query.filter(
                    or_(self._model.mobile == str(mobile_or_token),
                        self._model.email == str(mobile_or_token))).first()
            elif not hasattr(self._model, 'mobile') and hasattr(
                    self._model, 'email'):
                user = self._model.query.filter(
                    self._model.email == str(mobile_or_token)).first()

            if user and user.active and user.check_password(password):
                # set token
                user.generate_auth_token()
                g.current_user = user
                g.token_used = False
                return True

        if self._share:
            return True
        raise_401_response(message=u'用户名或者密码错误')

    def __init__(self, app=None, db=None):
        self.app = app
        self.db = db
        self.auth = HTTPBasicAuth()
        self.auth.error_handler(unauthorized)
        self.auth.verify_password(self._verify_password)

        if app is not None and db is not None:
            self.init_app(app, db)

    def init_app(self, app, db):
        if app is None:
            raise ()

        self.app = app
        self.db = db
Exemple #2
0
class Auth:
    def __init__(self):
        """Creates access control class for authentication and authorization."""
        self._basic_auth = HTTPBasicAuth()
        self._token_auth = HTTPTokenAuth()
        self._auth = MultiAuth(self._basic_auth, self._token_auth)
        self._resources = {}

    def error_handler(self, f: typing.Callable) -> typing.NoReturn:
        """Set error handler for Authentication Errors.

        :param f: error handler.
        :return: NoReturn
        """
        self._token_auth.error_handler(f)
        self._basic_auth.error_handler(f)

    def verify_password(self, f: typing.Callable) -> typing.Any:
        """ Verifies basic password.

        :param f: function defining verification process.
        :return: Any
        """
        return self._basic_auth.verify_password(f)

    def verify_token(self, f: typing.Callable) -> typing.Any:
        """ Verifies token.

        :param f: function defining verification process.
        :return: Any
        """
        return self._token_auth.verify_token(f)

    def login_required(self,
                       f: typing.Callable = None,
                       role: typing.Text = None) -> typing.Any:
        """ Identifies as login required for provided function {f}.

        :param f: input function.
        :param role: user role
        :return: func
        """
        return self._auth.login_required(f, role)
Exemple #3
0
class Web:
    """
    The Web object is the OnionShare web server, powered by flask
    """

    REQUEST_LOAD = 0
    REQUEST_STARTED = 1
    REQUEST_PROGRESS = 2
    REQUEST_CANCELED = 3
    REQUEST_RATE_LIMIT = 4
    REQUEST_UPLOAD_FILE_RENAMED = 5
    REQUEST_UPLOAD_SET_DIR = 6
    REQUEST_UPLOAD_FINISHED = 7
    REQUEST_UPLOAD_CANCELED = 8
    REQUEST_INDIVIDUAL_FILE_STARTED = 9
    REQUEST_INDIVIDUAL_FILE_PROGRESS = 10
    REQUEST_INDIVIDUAL_FILE_CANCELED = 11
    REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 12
    REQUEST_OTHER = 13
    REQUEST_INVALID_PASSWORD = 14

    def __init__(self, common, is_gui, mode="share"):
        self.common = common
        self.common.log("Web", "__init__",
                        "is_gui={}, mode={}".format(is_gui, mode))

        # The flask app
        self.app = Flask(
            __name__,
            static_folder=self.common.get_resource_path("static"),
            static_url_path="/static_".format(
                self.common.random_string(16)
            ),  # randomize static_url_path to avoid making /static unusable
            template_folder=self.common.get_resource_path("templates"),
        )
        self.app.secret_key = self.common.random_string(8)
        self.generate_static_url_path()
        self.auth = HTTPBasicAuth()
        self.auth.error_handler(self.error401)

        # Verbose mode?
        if self.common.verbose:
            self.verbose_mode()

        # Are we running in GUI mode?
        self.is_gui = is_gui

        # If the user stops the server while a transfer is in progress, it should
        # immediately stop the transfer. In order to make it thread-safe, stop_q
        # is a queue. If anything is in it, then the user stopped the server
        self.stop_q = queue.Queue()

        # Are we using receive mode?
        self.mode = mode
        if self.mode == "receive":
            # Use custom WSGI middleware, to modify environ
            self.app.wsgi_app = ReceiveModeWSGIMiddleware(
                self.app.wsgi_app, self)
            # Use a custom Request class to track upload progess
            self.app.request_class = ReceiveModeRequest

        # Starting in Flask 0.11, render_template_string autoescapes template variables
        # by default. To prevent content injection through template variables in
        # earlier versions of Flask, we force autoescaping in the Jinja2 template
        # engine if we detect a Flask version with insecure default behavior.
        if Version(flask_version) < Version("0.11"):
            # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc
            Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape

        self.security_headers = [
            ("X-Frame-Options", "DENY"),
            ("X-Xss-Protection", "1; mode=block"),
            ("X-Content-Type-Options", "nosniff"),
            ("Referrer-Policy", "no-referrer"),
            ("Server", "OnionShare"),
        ]

        self.q = queue.Queue()
        self.password = None

        self.reset_invalid_passwords()

        self.done = False

        # shutting down the server only works within the context of flask, so the easiest way to do it is over http
        self.shutdown_password = self.common.random_string(16)

        # Keep track if the server is running
        self.running = False

        # Define the web app routes
        self.define_common_routes()

        # Create the mode web object, which defines its own routes
        self.share_mode = None
        self.receive_mode = None
        self.website_mode = None
        if self.mode == "share":
            self.share_mode = ShareModeWeb(self.common, self)
        elif self.mode == "receive":
            self.receive_mode = ReceiveModeWeb(self.common, self)
        elif self.mode == "website":
            self.website_mode = WebsiteModeWeb(self.common, self)

    def get_mode(self):
        if self.mode == "share":
            return self.share_mode
        elif self.mode == "receive":
            return self.receive_mode
        elif self.mode == "website":
            return self.website_mode
        else:
            return None

    def generate_static_url_path(self):
        # The static URL path has a 128-bit random number in it to avoid having name
        # collisions with files that might be getting shared
        self.static_url_path = "/static_{}".format(
            self.common.random_string(16))
        self.common.log(
            "Web",
            "generate_static_url_path",
            "new static_url_path is {}".format(self.static_url_path),
        )

        # Update the flask route to handle the new static URL path
        self.app.static_url_path = self.static_url_path
        self.app.add_url_rule(
            self.static_url_path + "/<path:filename>",
            endpoint="static",
            view_func=self.app.send_static_file,
        )

    def define_common_routes(self):
        """
        Common web app routes between all modes.
        """
        @self.auth.get_password
        def get_pw(username):
            if username == "onionshare":
                return self.password
            else:
                return None

        @self.app.before_request
        def conditional_auth_check():
            # Allow static files without basic authentication
            if request.path.startswith(self.static_url_path + "/"):
                return None

            # If public mode is disabled, require authentication
            if not self.common.settings.get("public_mode"):

                @self.auth.login_required
                def _check_login():
                    return None

                return _check_login()

        @self.app.errorhandler(404)
        def not_found(e):
            mode = self.get_mode()
            history_id = mode.cur_history_id
            mode.cur_history_id += 1
            return self.error404(history_id)

        @self.app.route("/<password_candidate>/shutdown")
        def shutdown(password_candidate):
            """
            Stop the flask web server, from the context of an http request.
            """
            if password_candidate == self.shutdown_password:
                self.force_shutdown()
                return ""
            abort(404)

        if self.mode != "website":

            @self.app.route("/favicon.ico")
            def favicon():
                return send_file("{}/img/favicon.ico".format(
                    self.common.get_resource_path("static")))

    def error401(self):
        auth = request.authorization
        if auth:
            if (auth["username"] == "onionshare"
                    and auth["password"] not in self.invalid_passwords):
                print("Invalid password guess: {}".format(auth["password"]))
                self.add_request(Web.REQUEST_INVALID_PASSWORD,
                                 data=auth["password"])

                self.invalid_passwords.append(auth["password"])
                self.invalid_passwords_count += 1

                if self.invalid_passwords_count == 20:
                    self.add_request(Web.REQUEST_RATE_LIMIT)
                    self.force_shutdown()
                    print(
                        "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share."
                    )

        r = make_response(
            render_template("401.html", static_url_path=self.static_url_path),
            401)
        return self.add_security_headers(r)

    def error403(self):
        self.add_request(Web.REQUEST_OTHER, request.path)
        r = make_response(
            render_template("403.html", static_url_path=self.static_url_path),
            403)
        return self.add_security_headers(r)

    def error404(self, history_id):
        self.add_request(
            self.REQUEST_INDIVIDUAL_FILE_STARTED,
            "{}".format(request.path),
            {
                "id": history_id,
                "status_code": 404
            },
        )

        self.add_request(Web.REQUEST_OTHER, request.path)
        r = make_response(
            render_template("404.html", static_url_path=self.static_url_path),
            404)
        return self.add_security_headers(r)

    def error405(self):
        r = make_response(
            render_template("405.html", static_url_path=self.static_url_path),
            405)
        return self.add_security_headers(r)

    def add_security_headers(self, r):
        """
        Add security headers to a request
        """
        for header, value in self.security_headers:
            r.headers.set(header, value)
        # Set a CSP header unless in website mode and the user has disabled it
        if (not self.common.settings.get("csp_header_disabled")
                or self.mode != "website"):
            r.headers.set(
                "Content-Security-Policy",
                "default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:;",
            )
        return r

    def _safe_select_jinja_autoescape(self, filename):
        if filename is None:
            return True
        return filename.endswith((".html", ".htm", ".xml", ".xhtml"))

    def add_request(self, request_type, path=None, data=None):
        """
        Add a request to the queue, to communicate with the GUI.
        """
        self.q.put({"type": request_type, "path": path, "data": data})

    def generate_password(self, persistent_password=None):
        self.common.log(
            "Web",
            "generate_password",
            "persistent_password={}".format(persistent_password),
        )
        if persistent_password != None and persistent_password != "":
            self.password = persistent_password
            self.common.log(
                "Web",
                "generate_password",
                'persistent_password sent, so password is: "{}"'.format(
                    self.password),
            )
        else:
            self.password = self.common.build_password()
            self.common.log(
                "Web",
                "generate_password",
                'built random password: "******"'.format(self.password),
            )

    def verbose_mode(self):
        """
        Turn on verbose mode, which will log flask errors to a file.
        """
        flask_log_filename = os.path.join(self.common.build_data_dir(),
                                          "flask.log")
        log_handler = logging.FileHandler(flask_log_filename)
        log_handler.setLevel(logging.WARNING)
        self.app.logger.addHandler(log_handler)

    def reset_invalid_passwords(self):
        self.invalid_passwords_count = 0
        self.invalid_passwords = []

    def force_shutdown(self):
        """
        Stop the flask web server, from the context of the flask app.
        """
        # Shutdown the flask service
        try:
            func = request.environ.get("werkzeug.server.shutdown")
            if func is None:
                raise RuntimeError("Not running with the Werkzeug Server")
            func()
        except:
            pass
        self.running = False

    def start(self, port, stay_open=False, public_mode=False, password=None):
        """
        Start the flask web server.
        """
        self.common.log(
            "Web",
            "start",
            "port={}, stay_open={}, public_mode={}, password={}".format(
                port, stay_open, public_mode, password),
        )

        self.stay_open = stay_open

        # Make sure the stop_q is empty when starting a new server
        while not self.stop_q.empty():
            try:
                self.stop_q.get(block=False)
            except queue.Empty:
                pass

        # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220)
        if os.path.exists("/usr/share/anon-ws-base-files/workstation"):
            host = "0.0.0.0"
        else:
            host = "127.0.0.1"

        self.running = True
        self.app.run(host=host, port=port, threaded=True)

    def stop(self, port):
        """
        Stop the flask web server by loading /shutdown.
        """
        self.common.log("Web", "stop", "stopping server")

        # Let the mode know that the user stopped the server
        self.stop_q.put(True)

        # To stop flask, load http://shutdown:[shutdown_password]@127.0.0.1/[shutdown_password]/shutdown
        # (We're putting the shutdown_password in the path as well to make routing simpler)
        if self.running:
            requests.get(
                "http://127.0.0.1:{}/{}/shutdown".format(
                    port, self.shutdown_password),
                auth=requests.auth.HTTPBasicAuth("onionshare", self.password),
            )

        # Reset any password that was in use
        self.password = None
Exemple #4
0
class BaseSecurity(object):

    _model = None

    _auth_model = None

    _ignore_auth = False

    def _get_user_from_auth_token(self, token):
        s = Serializer(current_app.config['SECRET_KEY'],
                       salt=current_app.config['SECRET_SALT'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None
        except BadSignature:
            return None
        return self._model.get_by_id(data['id'])

    def _verify_password(self, access_id_or_token, password):
        token = request.values.get('token', None)

        if request.method == 'OPTIONS' and not token:
            return True

        # 是否忽略验证
        if self._ignore_auth:
            return True

        # 登录名、密码登录
        if access_id_or_token and password:
            user_auth = self._auth_model.query.filter_by(
                access_id=access_id_or_token).first()
            if not user_auth or not user_auth.user:
                raise_401_response(message=u'该用户未注册')
            user = user_auth.user

            # FIXME: 0需要与数据库常量同步
            if (not user) or user.status != 0:
                raise_401_response(message=u'该用户未注册')
            if bcrypt.check_password_hash(user_auth.access_token, password):
                g.current_user = user
                g.token_used = False
                return True
            else:
                raise_401_response(message=u'用户名或密码错误')

        # 验证token
        if access_id_or_token:
            user = self._get_user_from_auth_token(access_id_or_token)
            if not user:
                raise_401_response(message=u'请重新登录')
            # FIXME: 0需要与数据库常量同步
            if (not user) or user.status != 0:
                raise_401_response(message=u'请重新登录')
            g.current_user = user
            g.token_used = True
            return True

        raise_401_response(message=u'请登录')

    def __init__(self, app=None, db=None):
        self.app = app
        self.db = db
        self.auth = HTTPBasicAuth()
        self.auth.error_handler(_unauthorized)
        self.auth.verify_password(self._verify_password)

        if app is not None and db is not None:
            self.init_app(app, db)

    def init_app(self, app, db):
        if app is None:
            raise

        self.app = app
        self.db = db
Exemple #5
0
        db = client['run']
        foo_collection = db['foo']
        foo_collection.insert_one({'message': 'db created'})
    mongo = PyMongo(app, uri=f'{mongodb_url}/run')
    app.config['SECRET_KEY'] = 'mql'
    app.config['mongo'] = mongo
    if not User.get_user('mql', app.config['mongo']):
        common_user = User(user_name='mql',
                           passwd_hash='python',
                           mongo=app.config['mongo'])
        common_user.save()

CORS(app, supports_credentials=True)
auth = HTTPBasicAuth()
# 此处修改了httpauth包中的error_handler,直接返回json信息
auth.error_handler(my_auth_error)


@app.route('/api/treemap', methods=['POST'])
# @app.route('/api/treemap', methods=['GET'])
def query_treemap():
    project_name = request.json.get('project_name')
    version_name = request.json.get('version_name')
    # project_name = 'alibaba/cooma'
    # version_name = '0.4.0'
    version = app.config['mongo'].db.versions.find_one({
        'project_name':
        project_name,
        'version_name':
        version_name
    })