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)