Esempio n. 1
0
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
Esempio n. 2
0
    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')
Esempio n. 3
0
    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)
Esempio n. 4
0
 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
Esempio n. 5
0
 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
Esempio n. 6
0
    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