def parse_uri(uri, parse_query=True): scheme, authority, path = parse_request_uri(uri) if path is None: raise HTTPSimpleResponse(http_client.BAD_REQUEST, "No path component") if b'#' in path: raise HTTPSimpleResponse(http_client.BAD_REQUEST, "Illegal #fragment in Request-URI.") if scheme: try: scheme = scheme.decode('ascii') except ValueError: raise HTTPSimpleResponse(http_client.BAD_REQUEST, 'Un-decodeable scheme') path, qs = path.partition(b'?')[::2] if parse_query: try: query = MultiDict.create_from_query_string(qs) except Exception: raise HTTPSimpleResponse(http_client.BAD_REQUEST, 'Unparseable query string') else: query = None try: path = '%2F'.join( unquote(x).decode('utf-8') for x in quoted_slash.split(path)) except ValueError as e: raise HTTPSimpleResponse(http_client.BAD_REQUEST, as_unicode(e)) path = tuple(filter(None, (x.replace('%2F', '/') for x in path.split('/')))) return scheme, path, query
def __init__(self, header_val): data = parse_http_dict(header_val) self.realm = data.get('realm') self.username = data.get('username') self.nonce = data.get('nonce') self.uri = data.get('uri') self.method = data.get('method') self.response = data.get('response') self.algorithm = data.get('algorithm', 'MD5').upper() self.cnonce = data.get('cnonce') self.opaque = data.get('opaque') self.qop = data.get('qop', '').lower() self.nonce_count = data.get('nc') if self.algorithm not in self.valid_algorithms: raise HTTPSimpleResponse(httplib.BAD_REQUEST, 'Unsupported digest algorithm') if not (self.username and self.realm and self.nonce and self.uri and self.response): raise HTTPSimpleResponse( httplib.BAD_REQUEST, 'Digest algorithm required fields missing') if self.qop: if self.qop not in self.valid_qops: raise HTTPSimpleResponse(httplib.BAD_REQUEST, 'Unsupported digest qop') if not (self.cnonce and self.nonce_count): raise HTTPSimpleResponse( httplib.BAD_REQUEST, 'qop present, but cnonce and nonce_count absent') else: if self.cnonce or self.nonce_count: raise HTTPSimpleResponse(httplib.BAD_REQUEST, 'qop missing')
def do_http_auth(self, data, endpoint): ban_key = data.remote_addr, data.forwarded_for if self.ban_list.is_banned(ban_key): raise HTTPForbidden( 'Too many login attempts', log='Too many login attempts from: %s' % (ban_key if data.forwarded_for else data.remote_addr)) auth = data.inheaders.get('Authorization') nonce_is_stale = False log_msg = None data.username = None if auth: scheme, rest = auth.partition(' ')[::2] scheme = scheme.lower() if scheme == 'digest': da = DigestAuth(rest.strip()) if validate_nonce(self.key_order, da.nonce, self.realm, self.secret): pw = self.user_credentials.get(da.username) if pw and da.validate_request(pw, data, self.log): nonce_is_stale = is_nonce_stale( da.nonce, self.max_age_seconds) if not nonce_is_stale: data.username = da.username return log_msg = 'Failed login attempt from: %s' % data.remote_addr self.ban_list.failed(ban_key) elif self.prefer_basic_auth and scheme == 'basic': try: un, pw = base64_decode(rest.strip()).partition(':')[::2] except ValueError: raise HTTPSimpleResponse( http_client.BAD_REQUEST, 'The username or password contained non-UTF8 encoded characters' ) if not un or not pw: raise HTTPSimpleResponse( http_client.BAD_REQUEST, 'The username or password was empty') if self.check(un, pw): data.username = un return log_msg = 'Failed login attempt from: %s' % data.remote_addr self.ban_list.failed(ban_key) else: raise HTTPSimpleResponse(http_client.BAD_REQUEST, 'Unsupported authentication method') if self.prefer_basic_auth: raise HTTPAuthRequired('Basic realm="%s"' % self.realm, log=log_msg) s = 'Digest realm="%s", nonce="%s", algorithm="MD5", qop="auth"' % ( self.realm, synthesize_nonce(self.key_order, self.realm, self.secret)) if nonce_is_stale: s += ', stale="true"' raise HTTPAuthRequired(s, log=log_msg)
def dispatch(self, data): endpoint_, args = self.find_route(data.path) if data.method not in endpoint_.methods: raise HTTPSimpleResponse(httplib.METHOD_NOT_ALLOWED) self.init_session(endpoint_, data) ans = endpoint_(self.ctx, data, *args) self.finalize_session(endpoint_, data, ans) return ans
def validate_request(self, pw, data, log=None): # We should also be checking for replay attacks by using nonce_count, # however, various HTTP clients, most prominently Firefox dont # implement nonce-counts correctly, so we cannot do the check. # https://bugzil.la/114451 path = parse_uri(self.uri.encode('utf-8'))[1] if path != data.path: if log is not None: log.warn('Authorization URI mismatch: %s != %s from client: %s' % ( data.path, path, data.remote_addr)) raise HTTPSimpleResponse(httplib.BAD_REQUEST, 'The uri in the Request Line and the Authorization header do not match') return self.response is not None and data.path == path and self.request_digest(pw, data) == self.response
def dispatch(self, data): endpoint_, args = self.find_route(data.path) if data.method not in endpoint_.methods: raise HTTPSimpleResponse(http_client.METHOD_NOT_ALLOWED) self.read_cookies(data) if endpoint_.auth_required and self.auth_controller is not None: self.auth_controller(data, endpoint_) if endpoint_.ok_code is not None: data.status_code = endpoint_.ok_code self.init_session(endpoint_, data) if endpoint_.needs_db_write: self.ctx.check_for_write_access(data) ans = endpoint_(self.ctx, data, *args) self.finalize_session(endpoint_, data, ans) outheaders = data.outheaders pp = endpoint_.postprocess if pp is not None: ans = pp(self.ctx, data, endpoint_, ans) cc = endpoint_.cache_control if cc is not False and 'Cache-Control' not in data.outheaders: if cc is None or cc == 'no-cache': outheaders['Expires'] = http_date( 10000.0) # A date in the past outheaders['Cache-Control'] = 'no-cache, must-revalidate' outheaders['Pragma'] = 'no-cache' elif isinstance(cc, numbers.Number): cc = int(60 * 60 * cc) outheaders['Cache-Control'] = 'public, max-age=%d' % cc if cc == 0: cc -= 100000 outheaders['Expires'] = http_date(cc + time.time()) else: ctype, max_age = cc max_age = int(60 * 60 * max_age) outheaders['Cache-Control'] = '%s, max-age=%d' % (ctype, max_age) if max_age == 0: max_age -= 100000 outheaders['Expires'] = http_date(max_age + time.time()) return ans