github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/families/identity/sawtooth_identity/processor/handler.py (about)

     1  # Copyright 2017 Intel Corporation
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  # ------------------------------------------------------------------------------
    15  
    16  import logging
    17  import hashlib
    18  
    19  
    20  from sawtooth_sdk.processor.handler import TransactionHandler
    21  from sawtooth_sdk.messaging.future import FutureTimeoutError
    22  from sawtooth_sdk.processor.exceptions import InvalidTransaction
    23  from sawtooth_sdk.processor.exceptions import InternalError
    24  from sawtooth_sdk.protobuf.setting_pb2 import Setting
    25  
    26  from sawtooth_identity.protobuf.identity_pb2 import Policy
    27  from sawtooth_identity.protobuf.identity_pb2 import PolicyList
    28  from sawtooth_identity.protobuf.identity_pb2 import Role
    29  from sawtooth_identity.protobuf.identity_pb2 import RoleList
    30  from sawtooth_identity.protobuf.identities_pb2 import IdentityPayload
    31  
    32  LOGGER = logging.getLogger(__name__)
    33  
    34  # The identity namespace is special: it is not derived from a hash.
    35  IDENTITY_NAMESPACE = '00001d'
    36  POLICY_PREFIX = '00'
    37  ROLE_PREFIX = '01'
    38  ALLOWED_SIGNER_SETTING = "sawtooth.identity.allowed_keys"
    39  
    40  # Constants to be used when constructing config namespace addresses
    41  _SETTING_NAMESPACE = '000000'
    42  _SETTING_MAX_KEY_PARTS = 4
    43  _SETTING_ADDRESS_PART_SIZE = 16
    44  # Number of seconds to wait for state operations to succeed
    45  STATE_TIMEOUT_SEC = 10
    46  
    47  
    48  def _setting_key_to_address(key):
    49      """Computes the address for the given setting key.
    50  
    51       Keys are broken into four parts, based on the dots in the string. For
    52       example, the key `a.b.c` address is computed based on `a`, `b`, `c` and
    53       padding. A longer key, for example `a.b.c.d.e`, is still
    54       broken into four parts, but the remaining pieces are in the last part:
    55       `a`, `b`, `c` and `d.e`.
    56  
    57       Each of these pieces has a short hash computed (the first
    58       _SETTING_ADDRESS_PART_SIZE characters of its SHA256 hash in hex), and is
    59       joined into a single address, with the config namespace
    60       (_SETTING_NAMESPACE) added at the beginning.
    61  
    62       Args:
    63           key (str): the setting key
    64       Returns:
    65           str: the computed address
    66       """
    67      # Split the key into _SETTING_MAX_KEY_PARTS parts, maximum, compute the
    68      # short hash of each, and then pad if necessary
    69      key_parts = key.split('.', maxsplit=_SETTING_MAX_KEY_PARTS - 1)
    70      addr_parts = [_setting_short_hash(byte_str=x.encode()) for x in key_parts]
    71      addr_parts.extend(
    72          [_SETTING_ADDRESS_PADDING] * (_SETTING_MAX_KEY_PARTS - len(addr_parts))
    73      )
    74      return _SETTING_NAMESPACE + ''.join(addr_parts)
    75  
    76  
    77  def _setting_short_hash(byte_str):
    78      # Computes the SHA 256 hash and truncates to be the length
    79      # of an address part (see _config_key_to_address for information on
    80      return hashlib.sha256(byte_str).hexdigest()[:_SETTING_ADDRESS_PART_SIZE]
    81  
    82  
    83  _SETTING_ADDRESS_PADDING = _setting_short_hash(byte_str=b'')
    84  ALLOWED_SIGNER_ADDRESS = _setting_key_to_address(
    85      "sawtooth.identity.allowed_keys")
    86  
    87  
    88  class IdentityTransactionHandler(TransactionHandler):
    89      @property
    90      def family_name(self):
    91          return 'sawtooth_identity'
    92  
    93      @property
    94      def family_versions(self):
    95          return ['1.0']
    96  
    97      @property
    98      def namespaces(self):
    99          return [IDENTITY_NAMESPACE]
   100  
   101      def apply(self, transaction, context):
   102          _check_allowed_transactor(transaction, context)
   103  
   104          payload = IdentityPayload()
   105          payload.ParseFromString(transaction.payload)
   106  
   107          id_type = payload.type
   108          data = payload.data
   109  
   110          if id_type == IdentityPayload.ROLE:
   111              _set_role(data, context)
   112  
   113          elif id_type == IdentityPayload.POLICY:
   114              _set_policy(data, context)
   115  
   116          else:
   117              raise InvalidTransaction("The IdentityType must be either a"
   118                                       " ROLE or a POLICY")
   119  
   120  
   121  def _check_allowed_transactor(transaction, context):
   122      header = transaction.header
   123  
   124      entries_list = _get_data(ALLOWED_SIGNER_ADDRESS, context)
   125      if not entries_list:
   126          raise InvalidTransaction(
   127              "The transaction signer is not authorized to submit transactions: "
   128              "{}".format(header.signer_public_key))
   129  
   130      setting = Setting()
   131      setting.ParseFromString(entries_list[0].data)
   132      for entry in setting.entries:
   133          if entry.key == "sawtooth.identity.allowed_keys":
   134              allowed_signer = entry.value.split(",")
   135              if header.signer_public_key in allowed_signer:
   136                  return
   137  
   138      raise InvalidTransaction(
   139          "The transction signer is not authorized to submit transactions: "
   140          "{}".format(header.signer_public_key))
   141  
   142  
   143  def _set_policy(data, context):
   144      new_policy = Policy()
   145      new_policy.ParseFromString(data)
   146  
   147      if not new_policy.entries:
   148          raise InvalidTransaction("Atleast one entry must be in a policy.")
   149  
   150      if not new_policy.name:
   151          raise InvalidTransaction("The name must be set in a policy.")
   152  
   153      # check entries in the policy
   154      for entry in new_policy.entries:
   155          if not entry.key:
   156              raise InvalidTransaction("Every policy entry must have a key.")
   157  
   158      address = _get_policy_address(new_policy.name)
   159      entries_list = _get_data(address, context)
   160  
   161      policy_list = PolicyList()
   162      policies = []
   163  
   164      if entries_list != []:
   165          policy_list.ParseFromString(entries_list[0].data)
   166  
   167          # sort all roles by using sorted(roles, policy.name)
   168          # if a policy with the same name exists, replace that policy
   169          policies = [
   170              x for x in policy_list.policies if x.name != new_policy.name
   171          ]
   172          policies.append(new_policy)
   173          policies = sorted(policies, key=lambda role: role.name)
   174      else:
   175          policies.append(new_policy)
   176  
   177      address = _get_policy_address(new_policy.name)
   178  
   179      # Store policy in a PolicyList incase of hash collisions
   180      new_policy_list = PolicyList(policies=policies)
   181      addresses = context.set_state({
   182          address: new_policy_list.SerializeToString()
   183      })
   184  
   185      if not addresses:
   186          LOGGER.warning('Failed to set policy %s at %s', new_policy.name,
   187                         address)
   188          raise InternalError('Unable to save policy {}'.format(new_policy.name))
   189  
   190      context.add_event(
   191          event_type="identity/update",
   192          attributes=[("updated", new_policy.name)])
   193      LOGGER.debug("Set policy : \n%s", new_policy)
   194  
   195  
   196  def _set_role(data, context):
   197      role = Role()
   198      role.ParseFromString(data)
   199  
   200      if not role.name:
   201          raise InvalidTransaction("The name must be set in a role")
   202      if not role.policy_name:
   203          raise InvalidTransaction("A role must contain a policy name.")
   204  
   205      # Check that the policy refernced exists
   206      policy_address = _get_policy_address(role.policy_name)
   207      entries_list = _get_data(policy_address, context)
   208  
   209      if entries_list == []:
   210          raise InvalidTransaction(
   211              "Cannot set Role: {}, the Policy: {} is not set.".format(
   212                  role.name, role.policy_name))
   213      else:
   214          policy_list = PolicyList()
   215          policy_list.ParseFromString(entries_list[0].data)
   216          exist = False
   217          for policy in policy_list.policies:
   218              if policy.name == role.policy_name:
   219                  exist = True
   220                  break
   221  
   222          if not exist:
   223              raise InvalidTransaction(
   224                  "Cannot set Role {}, the Policy {} is not set.".format(
   225                      role.name, role.policy_name))
   226  
   227      address = _get_role_address(role.name)
   228      entries_list = _get_data(address, context)
   229  
   230      # Store role in a Roleist incase of hash collisions
   231      role_list = RoleList()
   232      if entries_list != []:
   233          role_list.ParseFromString(entries_list[0].data)
   234  
   235      # sort all roles by using sorted(roles, Role.name)
   236      roles = [x for x in role_list.roles if x.name != role.name]
   237      roles.append(role)
   238      roles = sorted(roles, key=lambda role: role.name)
   239  
   240      # set RoleList at the address above.
   241      addresses = context.set_state({
   242          address:
   243          RoleList(roles=roles).SerializeToString()
   244      })
   245  
   246      if not addresses:
   247          LOGGER.warning('Failed to set role %s at %s', role.name, address)
   248          raise InternalError('Unable to save role {}'.format(role.name))
   249  
   250      context.add_event(
   251          event_type="identity/update", attributes=[("updated", role.name)])
   252      LOGGER.debug("Set role: \n%s", role)
   253  
   254  
   255  def _get_data(address, context):
   256      try:
   257          entries_list = context.get_state([address], timeout=STATE_TIMEOUT_SEC)
   258  
   259      except FutureTimeoutError:
   260          LOGGER.warning('Timeout occured on context.get_state([%s])', address)
   261          raise InternalError('Unable to get {}'.format(address))
   262  
   263      return entries_list
   264  
   265  
   266  def _to_hash(value):
   267      return hashlib.sha256(value.encode()).hexdigest()
   268  
   269  
   270  def _get_policy_address(policy_name):
   271      return IDENTITY_NAMESPACE + POLICY_PREFIX + _to_hash(policy_name)[:62]
   272  
   273  
   274  _MAX_KEY_PARTS = 4
   275  _FIRST_ADDRESS_PART_SIZE = 14
   276  _ADDRESS_PART_SIZE = 16
   277  _EMPTY_PART = _to_hash('')[:_ADDRESS_PART_SIZE]
   278  
   279  
   280  def _get_role_address(role_name):
   281      # split the key into 4 parts, maximum
   282      key_parts = role_name.split('.', maxsplit=_MAX_KEY_PARTS - 1)
   283  
   284      # compute the short hash of each part
   285      addr_parts = [_to_hash(key_parts[0])[:_FIRST_ADDRESS_PART_SIZE]]
   286      addr_parts += [_to_hash(x)[:_ADDRESS_PART_SIZE] for x in key_parts[1:]]
   287  
   288      # pad the parts with the empty hash, if needed
   289      addr_parts.extend([_EMPTY_PART] * (_MAX_KEY_PARTS - len(addr_parts)))
   290  
   291      return IDENTITY_NAMESPACE + ROLE_PREFIX + ''.join(addr_parts)