1import logging 2 3from .base import default_ts_skew_in_seconds, HawkAuthority, Resource 4from .util import (calculate_mac, 5 parse_authorization_header, 6 validate_credentials) 7 8__all__ = ['Sender'] 9log = logging.getLogger(__name__) 10 11 12class Sender(HawkAuthority): 13 """ 14 A Hawk authority that will emit requests and verify responses. 15 16 :param credentials: Dict of credentials with keys ``id``, ``key``, 17 and ``algorithm``. See :ref:`usage` for an example. 18 :type credentials: dict 19 20 :param url: Absolute URL of the request. 21 :type url: str 22 23 :param method: Method of the request. E.G. POST, GET 24 :type method: str 25 26 :param content=None: Byte string of request body. 27 :type content=None: str 28 29 :param content_type=None: content-type header value for request. 30 :type content_type=None: str 31 32 :param always_hash_content=True: 33 When True, ``content`` and ``content_type`` cannot be None. 34 Read :ref:`skipping-content-checks` to learn more. 35 :type always_hash_content=True: bool 36 37 :param nonce=None: 38 A string that when coupled with the timestamp will 39 uniquely identify this request to prevent replays. 40 If None, a nonce will be generated for you. 41 :type nonce=None: str 42 43 :param ext=None: 44 An external `Hawk`_ string. If not None, this value will be signed 45 so that the receiver can trust it. 46 :type ext=None: str 47 48 :param app=None: 49 A `Hawk`_ application string. If not None, this value will be signed 50 so that the receiver can trust it. 51 :type app=None: str 52 53 :param dlg=None: 54 A `Hawk`_ delegation string. If not None, this value will be signed 55 so that the receiver can trust it. 56 :type dlg=None: str 57 58 :param seen_nonce=None: 59 A callable that returns True if a nonce has been seen. 60 See :ref:`nonce` for details. 61 :type seen_nonce=None: callable 62 63 .. _`Hawk`: https://github.com/hueniverse/hawk 64 """ 65 #: Value suitable for an ``Authorization`` header. 66 request_header = None 67 68 def __init__(self, credentials, 69 url, 70 method, 71 content=None, 72 content_type=None, 73 always_hash_content=True, 74 nonce=None, 75 ext=None, 76 app=None, 77 dlg=None, 78 seen_nonce=None, 79 # For easier testing: 80 _timestamp=None): 81 82 self.reconfigure(credentials) 83 self.request_header = None 84 self.seen_nonce = seen_nonce 85 86 log.debug('generating request header') 87 self.req_resource = Resource(url=url, 88 credentials=self.credentials, 89 ext=ext, 90 app=app, 91 dlg=dlg, 92 nonce=nonce, 93 method=method, 94 content=content, 95 always_hash_content=always_hash_content, 96 timestamp=_timestamp, 97 content_type=content_type) 98 99 mac = calculate_mac('header', self.req_resource, 100 self.req_resource.gen_content_hash()) 101 self.request_header = self._make_header(self.req_resource, mac) 102 103 def accept_response(self, 104 response_header, 105 content=None, 106 content_type=None, 107 accept_untrusted_content=False, 108 localtime_offset_in_seconds=0, 109 timestamp_skew_in_seconds=default_ts_skew_in_seconds, 110 **auth_kw): 111 """ 112 Accept a response to this request. 113 114 :param response_header: 115 A `Hawk`_ ``Server-Authorization`` header 116 such as one created by :class:`mohawk.Receiver`. 117 :type response_header: str 118 119 :param content=None: Byte string of the response body received. 120 :type content=None: str 121 122 :param content_type=None: 123 Content-Type header value of the response received. 124 :type content_type=None: str 125 126 :param accept_untrusted_content=False: 127 When True, allow responses that do not hash their content or 128 allow None type ``content`` and ``content_type`` 129 arguments. Read :ref:`skipping-content-checks` 130 to learn more. 131 :type accept_untrusted_content=False: bool 132 133 :param localtime_offset_in_seconds=0: 134 Seconds to add to local time in case it's out of sync. 135 :type localtime_offset_in_seconds=0: float 136 137 :param timestamp_skew_in_seconds=60: 138 Max seconds until a message expires. Upon expiry, 139 :class:`mohawk.exc.TokenExpired` is raised. 140 :type timestamp_skew_in_seconds=60: float 141 142 .. _`Hawk`: https://github.com/hueniverse/hawk 143 """ 144 log.debug('accepting response {header}' 145 .format(header=response_header)) 146 147 parsed_header = parse_authorization_header(response_header) 148 149 resource = Resource(ext=parsed_header.get('ext', None), 150 content=content, 151 content_type=content_type, 152 # The following response attributes are 153 # in reference to the original request, 154 # not to the reponse header: 155 timestamp=self.req_resource.timestamp, 156 nonce=self.req_resource.nonce, 157 url=self.req_resource.url, 158 method=self.req_resource.method, 159 app=self.req_resource.app, 160 dlg=self.req_resource.dlg, 161 credentials=self.credentials, 162 seen_nonce=self.seen_nonce) 163 164 self._authorize( 165 'response', parsed_header, resource, 166 # Per Node lib, a responder macs the *sender's* timestamp. 167 # It does not create its own timestamp. 168 # I suppose a slow response could time out here. Maybe only check 169 # mac failures, not timeouts? 170 their_timestamp=resource.timestamp, 171 timestamp_skew_in_seconds=timestamp_skew_in_seconds, 172 localtime_offset_in_seconds=localtime_offset_in_seconds, 173 accept_untrusted_content=accept_untrusted_content, 174 **auth_kw) 175 176 def reconfigure(self, credentials): 177 validate_credentials(credentials) 178 self.credentials = credentials 179