Superset的权限控制
关于这一章,我们主要从几个方面来看下Superset如何使用Flask提供的权限框架,如何去自定义认证流程。如何去明确用户是否有权限去访问接口,也就是权限的访问流程。
- 如何自定义认证框架
在Flask框架之中,支持多种认证方式
# AUTH_OID : Is for OpenID # AUTH_DB : Is for database (username/password) # AUTH_LDAP : Is for LDAP # AUTH_REMOTE_USER : Is for using REMOTE_USER from web server |
其配置默认是AUTH_DB,也即是声明了通过账号密码的方式进行登录。
在声明完使用的认证方式之后。相关的组合配置项位于
flask_appbuilder.security.sqla.manager.SecurityManager下
register_views之中,在其中,根据auth_type进行了判断
if self.auth_type == AUTH_DB: self.user_view = self.userdbmodelview self.auth_view = self.authdbview() elif self.auth_type == AUTH_LDAP: |
如果我们想要客制化自己的登录流程,比如说复写一个AUTH_DB
那么就可以书写一个自己的类,继承AuthDBView
重写属于自己的login方法即可。
class CustomAuthDBView(AuthDBView): @expose(“/login/”, methods=[“GET”, “POST”]) def login(self): pass |
之后在superset.security.manager.SupersetSecurityManager类中覆盖原本的authview属性即可。
class SupersetSecurityManager( # pylint: disable=too-many-public-methods SecurityManager ): userstatschartview = None user_model = CustomUser authdbview = CustomAuthDBView |
这里我们还是以AuthDBView为例,看看他们内部做了什么
class AuthDBView(AuthView): login_template = “appbuilder/general/security/login_db.html” @expose(“/login/”, methods=[“GET”, “POST”]) |
首先是判断用户是否已经登录了。
如果没有登录,才会利用数据库查询是否存在相关的user
之后是login_user,这个方法中会将user保存在session之中。
if not force and not user.is_active: return False user_id = getattr(user, current_app.login_manager.id_attribute)() if remember: current_app.login_manager._update_request_context_with_user(user) |
登录完成之后进行相关的回调。
在说完了如何设置使用怎么样的认证方式和Flask中数据库的登录过程之后,我们看下Flask是如何判断用户有访问菜单以及api权限的过程。
在Superset中,绘制页面中存在的菜单入口在base.py
下的common_bootstrap_payload函数中
def common_bootstrap_payload(user: User) -> Dict[str, Any]: return { **(cached_common_bootstrap_data(user)), “flash_messages”: get_flashed_messages(with_categories=True), } |
在其中主要是 cached_common_bootstrap_data 函数之中
而在cached_common_bootstrap_data函数中,则是主要利用了menu = appbuilder.menu.get_data() 这一句代码获取到菜单。
而在appbuilder.menu.get_data之中,则利用get_user_menu_access函数获取到了所有有权限的菜单。
在内部调用了_get_user_permission_view_menus函数
在函数之中,根据user所拥有的role,role所挂载的permission_view来判断是否具有menu的访问权限。
这里我们展开说说Flask之中的权限框架
user绑定role
role绑定 permission_view
permission_view 主要由两部分组成,permission,view_menu
其中view_menu可以看作是资源,比如Superset中的Dataset,DataSource,Chart这一类的资源概念。
而permission则是可以对资源进行什么操作,比如can_read, can_write, can_get
这一类的实际操作
实际拼接起来,则显示为
can_read on Dataset
诸如这样的permission_view
那么上面的_get_user_permission_view_menus 函数就是查询当前用户所有可以menu_access的permission_view。
在获取到所有有权限的菜单之后,将所有的menu对象进行遍历,判断是否有权限,并进行递归拼接”childs”: self.get_data(menu=item.childs),最后进行返回。
for i, item in enumerate(menu): if not item.should_render(): continue if item.name == “-” and not i == len(menu) – 1: |
通过这种方式,获取到了当前用户可以看到的menu。
我们当然可以在menu = appbuilder.menu.get_data()之后做一些工作,从而修改或者添加我们想要客制化的菜单。
在说完了菜单之后,我们可以看下superset如何判断一个用户如何有权限访问一个api接口的,这里我们以
DashboardApi中的get_list为例,看下如何判断用户是否可以调用这个api
在这个api之上存在注解@has_access
has_access这个装饰器会将代码进行包装
def wraps(self, *args, **kwargs): permission_str = f”{PERMISSION_PREFIX}{f._permission_name}” if self.method_permission_name: _permission_name = self.method_permission_name.get(f.__name__) if _permission_name: permission_str = f”{PERMISSION_PREFIX}{_permission_name}” if permission_str in self.base_permissions and self.appbuilder.sm.has_access( permission_str, self.class_permission_name ): return f(self, *args, **kwargs) else: log.warning( LOGMSG_ERR_SEC_ACCESS_DENIED.format( permission_str, self.__class__.__name__ ) ) flash(as_unicode(FLAMSG_ERR_SEC_ACCESS_DENIED), “danger”) return redirect( url_for( self.appbuilder.sm.auth_view.__class__.__name__ + “.login”, next=request.url, ) ) f._permission_name = permission_str |
其中的重点在前半段
permission_str = f”{PERMISSION_PREFIX}{f._permission_name}” if self.method_permission_name: _permission_name = self.method_permission_name.get(f.__name__) if _permission_name: permission_str = f”{PERMISSION_PREFIX}{_permission_name}” if permission_str in self.base_permissions and self.appbuilder.sm.has_access( permission_str, self.class_permission_name ): return f(self, *args, **kwargs) |
就是拼接了permission_str 和 class_permission_name
然后调用了sm.has_access 来判断是否有权限,sm中的has_access类似上面的菜单判断权限,就是根据用户的role,来判断是否有这一条权限,从而返回true或false。
主要是拼接上面的permission_str 和 class_permission_name
class_permission_name中则是在类中声明的字段
class_permission_name = “Dashboard”
而声明_permission_name,会尝试读取类中的method_permission_name字段
method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP
利用Map进行转换,如果读取不到,则会使用方法名作为permission_name
最后拼接为
can_list on Dashboard
如果不想要声明在method_permission_name字段之中,可以利用
@permission_name(“get”)
注解来修改_permission_name.
那么这样就是框架的认证方式选择,以及如何去判断用户能看到哪些菜单,以及用户能访问哪些api的全过程总结。其中基本上都是Superset使用的Flask框架本身的功能。