【问题标题】:ndb models, decorators, nested functionsndb 模型、装饰器、嵌套函数
【发布时间】:2013-05-28 16:01:44
【问题描述】:

我正在为我的应用寻求帮助。首先是示例代码(从大约 2k 行中删除...),稍后我将尝试解释我要查找的内容:

from google.appengine.ext import ndb
import webapp2
import json

class User(ndb.Model):
  company_              = ndb.KeyProperty(repeated=True)

  @property
  def company(self):
    return {} if not self.company_ else self.company_

  @company.setter
  def company(self, value):
    if value:
      self.company_ = self.company_.expand(value) if self.company_ else [value]
    else:
      self.company_ = []
    self.put()

class Company(ndb.Model):
  administrator         = ndb.KeyProperty(kind=User, repeated=True)
  manager               = ndb.KeyProperty(kind=User, repeated=True)

  # FAKE decorator
  @staticmethod
  def administrator(handler):
    def check_requirements(self, *a, **kw):
      if True:
        return
      else:
        return handler(self, *a, **kw)
    return check_requirements

class BaseHandler(webapp2.RequestHandler):
  def jwrite(self, **kw):
    return self.response.out.write( json.dumps(kw) )

class require(BaseHandler):
  @staticmethod
  def login(handler):
    def check_requirements(self, *a, **kw):
      if not self.auth.get_user_by_session():
        self.redirect('/', abort=True)
      else:
        return handler(self, *a, **kw)
    return check_requirements

class ApiHandler(BaseHandler):
  @require.login
  def post(self, model, action, key=''):
    method = '_post_%s' % model
    try:
      getattr(self, method)(action, key)
    except Exception as error:
      return self.jwrite( error = error)

  def _post_company(self, action, key):

    if action == 'create':
      data = dict(self.request.POST)
      """ Company.create( data ) method:
          Populates Company instance with POST data.
          Assigns first user that created the company
            both administrator and manager roles.
      """
      key_ = Company.create( data ) 
      if key_:
        self.user.company = key_
      return

    elif action == 'delete':

      @Company.administrator
      def delete_all_user_companies(self):
        ndb.delete_multi( self.user.company )
        self.user.company = None
        return

      companies = ndb.get_multi( self.user.company )
      if self.user.key in map( lambda c: c.administrator, companies):
        delete_all_user_companies(self)

    elif action == 'update':

      @Company.manager
      def update_company(self, key):
        data = dict(self.request.POST)
        """ Company.update( key, data ) method:
            Populates Company instance with POST data
        """
        key_ = Company.update( key, data )
        if key_:
          return

      company = ndb.Key(Company, key).get()
      if self.user.key in company.manager.extend(company.administrator):
        update_company(self)

如您所见,我有 User 和 Company 模型。用户可以有多个公司,公司可以有多个用户,可以是管理员或经理。你会注意到一些装饰器和嵌套函数——大部分都是假的 (; 但这就是我要找的......

我正在使用 @require.login 装饰器进行基本的登录检查(我将它放在单独的类中,只是因为它在代码中看起来更清晰 - @require.login 与 @BaseHandler.require_login )。有了它,我已经“保护”了我的 API 的发布方法,现在我需要对角色进行额外的检查——管理员可以做一些经理不能做的事情。我将需要在其他几个地方进行此检查,所以我认为这将是装饰器功能的好地方,但我不知道如何编写它们。我的第一个问题是:

  1. 这个装饰器的好地方是什么?我应该把它放在 Company 类还是 ApiHandler 类的某个地方?我的第一直觉是将它放在 Company 类中,但我不确定如何处理范围 - 我需要以某种方式在其中获取用户实例(self.user.company 列表)......

  2. 接下来是 ma​​nager 装饰器。我如何将其写为装饰器:

      company = ndb.Key(Company, key).get()
      if self.user.key in company.manager.extend(company.administrator):
        update_company(self)
    

    并将其用作 @Company.manager@requre.manager,这取决于我对第一个问题的回答?

  3. administrator 的另一个装饰器,它有点复杂 - 我必须检查用户是否是他所有公司的管理员,并删除他所在的地方,同时保留他不在的地方:

      companies = ndb.get_multi( self.user.company )
      if self.user.key in map( lambda c: c.administrator, companies ):
        delete_all_user_companies(self)
    

    我什至不确定这个 map() 函数是否正确以及代码是否可以工作,还没有尝试过 - 它现在只是一个伪代码占位符......

  4. 最后一个问题:我应该担心 POST 请求黑客攻击吗?根据上面的示例代码,是否有可能某些用户可以发出自定义 POST 请求并删除或更新不属于他的公司?

任何帮助、cmets 或见解将不胜感激(;谢谢!

【问题讨论】:

    标签: python google-app-engine decorator app-engine-ndb


    【解决方案1】:

    我相信我已经解决了这个问题:

    from google.appengine.ext import ndb
    import webapp2
    import json
    
    class User(ndb.Model):
      company_              = ndb.KeyProperty(repeated=True)
    
      @property
      def company(self):
        return {} if not self.company_ else self.company_
    
      @company.setter
      def company(self, value):
        if value:
          # self.company_ = self.company_.expand(value) if self.company_ else [value]
          # Lists mutate when expanded. Code above was returning None
          self.company_ = self.company_ + [value] if self.company_ else [value]
        else:
          self.company_ = []
        self.put()
    
    class Company(ndb.Model):
      administrator         = ndb.KeyProperty(kind=User, repeated=True)
      manager               = ndb.KeyProperty(kind=User, repeated=True)
    
    class BaseHandler(webapp2.RequestHandler):
      def jwrite(self, **kw):
        return self.response.out.write( json.dumps(kw) )
    
    class require(BaseHandler):
      @staticmethod
      def login(handler):
        def check_requirements(self, *a, **kw):
          if not self.auth.get_user_by_session():
            self.redirect('/', abort=True)
          else:
            return handler(self, *a, **kw)
        return check_requirements
    
    class role(BaseHandler):
      @staticmethod
      def administrator(handler):
        def check_requirements(self, *a, **kw):
          # I didn't care much about optimizing queries
          # since this isn't frequent operation.
          # For more frequent calls, I'd consider projections.
          companies = ndb.get_multi( *a )
          # Next lines checks if current user is administrator 
          # for all companies passed to the function
          if not self.user.key in reduce(lambda x, y: x if x != y else y, map(lambda c: c.administrator, companies)):
            return self.jwrite( error = 'Permission denied. Administrator required.' )
          else:
            return handler(self, *a, **kw)
        return check_requirements
    
      @staticmethod
      def manager(handler):
        def check_requirements(self, *a, **kw):
          companies = ndb.get_multi( *a )
          # Next lines checks if current user is manager
          # or administrator (since admin has higher privileges) 
          # for all companies passed to the function
          if not self.user.key in reduce(lambda x, y: x if x != y else y, map(lambda c: c.manager + c.administrator, companies)):
            return self.jwrite( error = 'Permission denied. Manager or Administrator required.' )
          else:
            return handler(self, *a, **kw)
        return check_requirements
    
    class ApiHandler(BaseHandler):
      @require.login
      def post(self, model, action, key=''):
        method = '_post_%s' % model
        try:
          getattr(self, method)(action, key)
        except Exception as error:
          return self.jwrite( error = error)
    
      def _post_company(self, action, key):
    
        if action == 'create':
          data = dict(self.request.POST)
          """ Company.create( data ) method:
              Populates Company instance with POST data.
              Assigns first user that created the company
                both administrator and manager roles.
          """
          key_ = Company.create( data ) 
          if key_:
            self.user.company = key_
          return
    
        elif action == 'delete':
    
          @role.administrator
          def delete_all_user_companies(self, *a):
            ndb.delete_multi( *a )
            self.user.company = None
            return
    
          delete_all_user_companies( self, self.user.company )
    
        elif action == 'update':
    
          @role.manager
          def update_company(self, *a ):
            data = dict(self.request.POST)
            """ Company.update( key, data ) method:
                Populates Company instance with POST data
            """
            key_ = Company.update( key, data )
            if key_:
              return
    
          update_company(self, ndb.Key(Company, key))
    

    并回答我自己的问题:

    1. 我在 Company 类中命名时遇到问题 - 有同名的属性管理员和装饰器。因此,为了方便起见,我将装饰器移到了 API 中,移到了新的类(角色)中。当我编写装饰器时,我意识到我可以将它们用于任何其他模型(带有管理器和管理员字段),所以我想这是一个很好的调用(;

    2. 编写装饰器花了一些时间并尝试了映射和减少数组,但我已经成功地完成了。我不确定是否将参数传递给装饰器。也许我应该在装饰器之外进行查询?或者将匹配的项目传递给处理函数?我得调查一下……

    3. ...删除他所在的位置,同时保留他不是管理员的位置 这就是为什么我首先在​​装饰器中进行查询的原因。但仍然不确定它是否聪明(;

    4. 我仍然可以使用这个答案。

    希望这对某人有所帮助...

    【讨论】:

      猜你喜欢
      • 2015-06-11
      • 1970-01-01
      • 2015-08-19
      • 2016-11-18
      • 2016-06-11
      • 1970-01-01
      • 1970-01-01
      • 2021-12-05
      • 2020-01-15
      相关资源
      最近更新 更多