Skip to content

Commit

Permalink
Merge branch 'master' into feat-user-management-api
Browse files Browse the repository at this point in the history
  • Loading branch information
dpgaspar committed Mar 21, 2022
2 parents 790bc94 + f6f66fc commit 172ed09
Show file tree
Hide file tree
Showing 19 changed files with 259 additions and 181 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-18.04
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9.7]
python-version: [3.7, 3.8, 3.9.7]
env:
SQLALCHEMY_DATABASE_URI:
postgresql+psycopg2://pguser:pguserpassword@127.0.0.1:15432/app
Expand Down Expand Up @@ -153,7 +153,7 @@ jobs:
runs-on: ubuntu-18.04
strategy:
matrix:
python-version: [3.6, 3.7]
python-version: [3.7]
services:
mssql:
image: mongo:4.4.1-bionic
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
Flask-AppBuilder ChangeLog
==========================

Improvements and Bug fixes on 4.0.0
-----------------------------------

- chore: major bumps Flask, Click, PyJWT and flask-jwt-extended (#1817) [Daniel Vaz Gaspar]
[Breaking changes]

Improvements and Bug fixes on 3.4.5
-----------------------------------

- test: Add test for `export-roles --indent`'s argument “duck casting” to int (#1811) [Étienne Boisseau-Sierra]
- fix: next url on login (OAuth, OID, DB) (#1804) [Daniel Vaz Gaspar]
- docs: Update doc i18 to flask_babel (#1792) [Federico Padua]
- feat(cli): allow `export-roles` to be beautified (#1724) [Étienne Boisseau-Sierra]

Improvements and Bug fixes on 3.4.4
-----------------------------------

Expand Down
11 changes: 7 additions & 4 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ can run a subset of tests targeting only Postgres.
$ docker-compose up -d
2 - Run Postgres tests

.. code-block:: bash
$ nosetests flask_appbuilder.tests
You can also use tox

.. code-block:: bash
$ tox -e postgres
Expand Down Expand Up @@ -64,16 +69,14 @@ Using Postgres

.. code-block:: bash
$ nosetests -v flask_appbuilder.tests.test_0_fixture
$ nosetests -v flask_appbuilder.tests.test_A_fixture
4 - Run a single test

.. code-block:: bash
$ nosetests -v flask_appbuilder.tests.test_api:APITestCase.test_get_item_dotted_mo_notation
.. note::

If your using SQLite3, the location of the db is: ./flask_appbuilder/tests/app.db
Expand Down
46 changes: 0 additions & 46 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,52 +40,6 @@ Change Log

`Versions <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/CHANGELOG.rst>`_ for further detail on what changed.

BREAKING CHANGE on 3.0.0 (OAuth)

Major version 3, changed it's **OAuth** dependency from flask-oauth to authlib, due to this OAuth configuration
changed:

Before:

.. code-block::
OAUTH_PROVIDERS = [
{'name':'google', 'icon':'fa-google', 'token_key':'access_token',
'remote_app': {
'consumer_key':'GOOGLE KEY',
'consumer_secret':'GOOGLE SECRET',
'base_url':'https://www.googleapis.com/oauth2/v2/',
'request_token_params':{
'scope': 'email profile'
},
'request_token_url':None,
'access_token_url':'https://accounts.google.com/o/oauth2/token',
'authorize_url':'https://accounts.google.com/o/oauth2/auth'}
}
]
Now:

.. code-block::
OAUTH_PROVIDERS = [
{'name':'google', 'icon':'fa-google', 'token_key':'access_token',
'remote_app': {
'client_id':'GOOGLE KEY',
'client_secret':'GOOGLE SECRET',
'api_base_url':'https://www.googleapis.com/oauth2/v2/',
'client_kwargs':{
'scope': 'email profile'
},
'request_token_url':None,
'access_token_url':'https://accounts.google.com/o/oauth2/token',
'authorize_url':'https://accounts.google.com/o/oauth2/auth'}
}
]
Also make sure you change your dependency for flask-oauth to `authlib <https://github.com/lepture/authlib>`_


Fixes, Bugs and contributions
-----------------------------

Expand Down
86 changes: 86 additions & 0 deletions docs/breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
BREAKING CHANGES
================

Version 4.0.0
-------------

- Drops python 3.6 support
- Removed config key `AUTH_STRICT_RESPONSE_CODES`, it's always strict now.
- Removes `Flask-OpenID` dependency (you can install it has an extra dependency `pip install flask-appbuilder[openid]`)
- Major version bumps on following packages

**Flask from 1.X to 2.X**

Breaking changes: https://flask.palletsprojects.com/en/2.0.x/changes/#version-2-0-0

**flask-jwt-extended 3.X to 4.X:**

Breaking changes: https://flask-jwt-extended.readthedocs.io/en/stable/v4_upgrade_guide/

**Jinja2 2.X to 3.X**

Breaking changes: https://jinja.palletsprojects.com/en/3.0.x/changes/#version-3-0-0

**Werkzeug 1.X to 2.X**

https://werkzeug.palletsprojects.com/en/2.0.x/changes/#version-2-0-0

The following packages are probably not impactful to you:

**pyJWT 1.X to 2.X:**

Breaking changes: https://pyjwt.readthedocs.io/en/stable/changelog.html#v2-0-0

**Click 7.X to 8.X:**

Breaking changes: https://click.palletsprojects.com/en/8.0.x/changes/#version-8-0-0

**itsdangerous 1.X to 2.X**

Breaking changes: https://github.com/pallets/itsdangerous/blob/main/CHANGES.rst#version-200

Version 3.0.0 (OAuth)
---------------------

Major version 3, changed it's **OAuth** dependency from flask-oauth to authlib, due to this OAuth configuration
changed:

Before:

.. code-block::
OAUTH_PROVIDERS = [
{'name':'google', 'icon':'fa-google', 'token_key':'access_token',
'remote_app': {
'consumer_key':'GOOGLE KEY',
'consumer_secret':'GOOGLE SECRET',
'base_url':'https://www.googleapis.com/oauth2/v2/',
'request_token_params':{
'scope': 'email profile'
},
'request_token_url':None,
'access_token_url':'https://accounts.google.com/o/oauth2/token',
'authorize_url':'https://accounts.google.com/o/oauth2/auth'}
}
]
Now:

.. code-block::
OAUTH_PROVIDERS = [
{'name':'google', 'icon':'fa-google', 'token_key':'access_token',
'remote_app': {
'client_id':'GOOGLE KEY',
'client_secret':'GOOGLE SECRET',
'api_base_url':'https://www.googleapis.com/oauth2/v2/',
'client_kwargs':{
'scope': 'email profile'
},
'request_token_url':None,
'access_token_url':'https://accounts.google.com/o/oauth2/token',
'authorize_url':'https://accounts.google.com/o/oauth2/auth'}
}
]
Also make sure you change your dependency for flask-oauth to `authlib <https://github.com/lepture/authlib>`_
5 changes: 0 additions & 5 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,6 @@ Use config.py to configure the following parameters. By default it will use SQLL
| AUTH_ROLE_PUBLIC | Special Role that holds the public | No |
| | permissions, no authentication needed. | |
+----------------------------------------+--------------------------------------------+-----------+
| AUTH_STRICT_RESPONSE_CODES | When True, protected endpoints will return | No |
| | HTTP 403 instead of 401. This option will | |
| | be removed and default to True on the next | |
| | major release. defaults to False | |
+----------------------------------------+--------------------------------------------+-----------+
| AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS| Allow REST API login with alternative auth | No |
| True|False | providers (default False) | |
+----------------------------------------+--------------------------------------------+-----------+
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Contents:
diagrams
api
versionmigration

breaking

Indices and tables
==================
Expand Down
2 changes: 1 addition & 1 deletion flask_appbuilder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__author__ = "Daniel Vaz Gaspar"
__version__ = "3.4.4"
__version__ = "4.0.0"

from .actions import action # noqa: F401
from .api import ModelRestApi # noqa: F401
Expand Down
4 changes: 2 additions & 2 deletions flask_appbuilder/security/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
create_access_token,
create_refresh_token,
get_jwt_identity,
jwt_refresh_token_required,
jwt_required,
)
from marshmallow import ValidationError

Expand Down Expand Up @@ -115,7 +115,7 @@ def login(self) -> Response:
return self.response(200, **resp)

@expose("/refresh", methods=["POST"])
@jwt_refresh_token_required
@jwt_required(refresh=True)
@safe
def refresh(self) -> Response:
"""
Expand Down
25 changes: 6 additions & 19 deletions flask_appbuilder/security/decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import functools
import logging
from typing import TYPE_CHECKING

from flask import (
current_app,
Expand All @@ -24,22 +23,8 @@

log = logging.getLogger(__name__)

if TYPE_CHECKING:
from flask_appbuilder.api import BaseApi


def response_unauthorized(base_class: "BaseApi") -> Response:
if current_app.config.get("AUTH_STRICT_RESPONSE_CODES", False):
return base_class.response_403()
return base_class.response_401()


def response_unauthorized_mvc() -> Response:
status_code = 401
if current_app.appbuilder.sm.current_user and current_app.config.get(
"AUTH_STRICT_RESPONSE_CODES", False
):
status_code = 403
def response_unauthorized_mvc(status_code: int) -> Response:
response = make_response(
jsonify({"message": str(FLAMSG_ERR_SEC_ACCESS_DENIED), "severity": "danger"}),
status_code,
Expand Down Expand Up @@ -88,7 +73,7 @@ def wraps(self, *args, **kwargs):
class_permission_name = self.class_permission_name
# Check if permission is allowed on the class
if permission_str not in self.base_permissions:
return response_unauthorized(self)
return self.response_403()
# Check if the resource is public
if current_app.appbuilder.sm.is_item_public(
permission_str, class_permission_name
Expand Down Expand Up @@ -116,7 +101,7 @@ def wraps(self, *args, **kwargs):
permission_str, class_permission_name
)
)
return response_unauthorized(self)
return self.response_403()

f._permission_name = permission_str
return functools.update_wrapper(wraps, f)
Expand Down Expand Up @@ -194,7 +179,9 @@ def wraps(self, *args, **kwargs):
permission_str, self.__class__.__name__
)
)
return response_unauthorized_mvc()
if not current_user.is_authenticated:
return response_unauthorized_mvc(401)
return response_unauthorized_mvc(403)

f._permission_name = permission_str
return functools.update_wrapper(wraps, f)
Expand Down
12 changes: 7 additions & 5 deletions flask_appbuilder/security/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def create_jwt_manager(self, app) -> JWTManager:
"""
jwt_manager = JWTManager()
jwt_manager.init_app(app)
jwt_manager.user_loader_callback_loader(self.load_user_jwt)
jwt_manager.user_lookup_loader(self.load_user_jwt)
return jwt_manager

def create_builtin_roles(self):
Expand Down Expand Up @@ -871,7 +871,8 @@ def auth_user_db(self, username, password):
)
log.info(LOGMSG_WAR_SEC_LOGIN_FAILED.format(username))
# Balance failure and success
self.noop_user_update(first_user)
if first_user:
self.noop_user_update(first_user)
return None
elif check_password_hash(user.password, password):
self.update_user_auth_stat(user, True)
Expand Down Expand Up @@ -1499,7 +1500,7 @@ def _get_user_permission_view_menus(
result.update(pvms_names)
return result

def has_access(self, permission_name, view_name):
def has_access(self, permission_name: str, view_name: str) -> bool:
"""
Check if current user or public has access to view or menu
"""
Expand Down Expand Up @@ -2036,8 +2037,9 @@ def import_roles(self, path: str) -> None:
def load_user(self, pk):
return self.get_user_by_id(int(pk))

def load_user_jwt(self, pk):
user = self.load_user(pk)
def load_user_jwt(self, _jwt_header, jwt_data):
identity = jwt_data["sub"]
user = self.load_user(identity)
# Set flask g.user to JWT user, we can't do it on before request
g.user = user
return user
Expand Down
1 change: 0 additions & 1 deletion flask_appbuilder/tests/config_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"SQLALCHEMY_DATABASE_URI"
) or "sqlite:///" + os.path.join(basedir, "app.db")

AUTH_STRICT_RESPONSE_CODES = False
SECRET_KEY = "thisismyscretkey"
SQLALCHEMY_TRACK_MODIFICATIONS = False
WTF_CSRF_ENABLED = False
Expand Down
Loading

0 comments on commit 172ed09

Please sign in to comment.