async fn authenticate<U: IntoUrl>(
        &self,
        method: Method,
        url: U,
        expected_scope: &Scope,
    ) -> Result<HeaderMap> {
        // since multiple requests can be fired off at the same time, special care has
        // to be taken when accessing / updating the authentication headers cache to
        // prevent multiple threads from performing authentication requests for the same
        // scope (wasting time and bandwidth).

        // first, acquire a lock on the entire cache
        let mut scope_cache = self.scope_cache.lock().await;

        // check if the expected_scope already exists in the cache
        if let Some(headers) = scope_cache.get(expected_scope) {
            // if it exists, that means the scope is either already authenticated, or a
            // task is currently authenticating it.
            //
            // In the latter case, this task should block until the authentication flow is
            // completed, _without_ blocking other tasks from querying the cache. As such,
            // the outer cache lock is dropped prior to waiting on the inner lock.
            std::mem::drop(scope_cache);
            let headers = headers.lock().await;

            // TODO: store expiration time alongside scope, checking for expiry
            return Ok(headers.clone());
        };

        // alright, look like this task gets to authenticate this request
        debug!("Not authenticated for scope {:?}", expected_scope);

        // insert a new entry into the scope cache, and immediately acquire it's inner
        // lock.
        //
        // This call to .lock() will not block, as this task holds the outer cache lock,
        // preventing any other tasks from calling .get() in the brief period of
        // time between insertion and lock acquisition.
        scope_cache.insert(expected_scope.clone(), HeaderMap::new());
        let headers = scope_cache
            .get(expected_scope)
            .expect("this should not fail");
        let mut headers = headers.lock().await;
        std::mem::drop(scope_cache);

        // once auth flow is completed, this value will be updated, both the inner mutex
        // will automatically release.

        // perform unauthenticated request to see what sort of authorization is required
        let unauth_res = self
            .client
            .request(method, url)
            .send()
            .await
            .context(AuthError::EndpointNoResponse)?;
        trace!("Unauth res: {:#?}", unauth_res);

        if unauth_res.status() != StatusCode::UNAUTHORIZED {
            // that's weird, but okay. Just pass up an empty auth header
            warn!("Attempted to authenticate with a URI that didn't require authentication");
            return Ok(HeaderMap::new());
        }

        let challenge = extract_challenge_from_headers(unauth_res.headers())?;
        let scope_str = challenge.parameters().get("scope").cloned();

        let auth_headers = match challenge.scheme() {
            ChallengeScheme::Bearer => self.authenticate_bearer(expected_scope, challenge).await?,
            _ => unreachable!(),
        };

        // Parse scopes from the scope_str
        let scopes = match scope_str {
            Some(scopes) => match scopes.parse::<Scopes>() {
                Ok(scopes) => Some(scopes),
                Err(_) => {
                    warn!("Returned scope doesn't conform to docker scope style");
                    debug!("Returned scope: {:?}", scopes);
                    None
                }
            },
            None => {
                warn!("WWW-Authenticate header did not include a scope attribute");
                None
            }
        };

        // update the scope cache appropriately
        match scopes {
            None => {
                // This happens when the server isn't using docker-style scopes, or didn't
                // return any scopes at all (e.g: when using Basic auth)
                // In this case, use the expected scope as the cache entry directly.
                *headers = auth_headers.clone()
            }
            Some(mut scopes) => {
                // Check that expected scopes used in containrs line up with the returned
                // scopes. If they don't, it's not a critical mission failure, but it is a
                // cache miss which could have been avoided.
                //
                // TODO: Infer best-guess scopes from URLs.
                // if such a system were put in place, this wouldn't be an warning. Instead, the
                // system would dynamically update it's guess for the expected scope.
                if scopes.is_disjoint(expected_scope) {
                    warn!("The expected scope did not overlap with the server's returned scopes.");
                    debug!("Expected {:?}", expected_scope);
                    debug!("Returned {:?}", scopes);
                    // Add the expected scope to the scope list to get some caching
                    scopes.add(expected_scope.clone());
                }

                // Associate the auth_headers with whatever scopes they correspond to
                let mut scope_cache = self.scope_cache.lock().await;
                for scope in scopes {
                    if scope.resource() != expected_scope.resource() {
                        scope_cache.insert(scope, auth_headers.clone());
                    } else {
                        *headers = auth_headers.clone()
                    }
                }
            }
        }

        Ok(headers.clone())
    }
/// Perform authentication flow for the given request, stashing the
/// resulting authentication headers in the cache. As a convenience, it
/// returns a valid authentication header.
Perform authentication flow for the given request, stashing the
resulting authentication headers in the cache. As a convenience, it
returns a valid authentication header.
Perform authentication flow for the given request stashing the resulting authentication headers in the cache . As a convenience it returns a valid authentication header .
async fn authenticate<U: IntoUrl>(
        &self,
        method: Method,
        url: U,
        expected_scope: &Scope,
    ) -> Result<HeaderMap> {
        // since multiple requests can be fired off at the same time, special care has
        // to be taken when accessing / updating the authentication headers cache to
        // prevent multiple threads from performing authentication requests for the same
        // scope (wasting time and bandwidth).
        // first, acquire a lock on the entire cache
        let mut scope_cache = self.scope_cache.lock().await;
        // check if the expected_scope already exists in the cache
        if let Some(headers) = scope_cache.get(expected_scope) {
            // if it exists, that means the scope is either already authenticated, or a
            // task is currently authenticating it.
            //
            // In the latter case, this task should block until the authentication flow is
            // completed, _without_ blocking other tasks from querying the cache. As such,
            // the outer cache lock is dropped prior to waiting on the inner lock.
            std::mem::drop(scope_cache);
            let headers = headers.lock().await;
            // TODO: store expiration time alongside scope, checking for expiry
            return Ok(headers.clone());
        };
        // alright, look like this task gets to authenticate this request
        debug!("Not authenticated for scope {:?}", expected_scope);
        // insert a new entry into the scope cache, and immediately acquire it's inner
        // lock.
        //
        // This call to .lock() will not block, as this task holds the outer cache lock,
        // preventing any other tasks from calling .get() in the brief period of
        // time between insertion and lock acquisition.
        scope_cache.insert(expected_scope.clone(), HeaderMap::new());
        let headers = scope_cache
            .get(expected_scope)
            .expect("this should not fail");
        let mut headers = headers.lock().await;
        std::mem::drop(scope_cache);
        // once auth flow is completed, this value will be updated, both the inner mutex
        // will automatically release.
        // perform unauthenticated request to see what sort of authorization is required
        let unauth_res = self
            .client
            .request(method, url)
            .send()
            .await
            .context(AuthError::EndpointNoResponse)?;
        trace!("Unauth res: {:#?}", unauth_res);
        if unauth_res.status() != StatusCode::UNAUTHORIZED {
            // that's weird, but okay. Just pass up an empty auth header
            warn!("Attempted to authenticate with a URI that didn't require authentication");
            return Ok(HeaderMap::new());
        }
        let challenge = extract_challenge_from_headers(unauth_res.headers())?;
        let scope_str = challenge.parameters().get("scope").cloned();
        let auth_headers = match challenge.scheme() {
            ChallengeScheme::Bearer => self.authenticate_bearer(expected_scope, challenge).await?,
            _ => unreachable!(),
        };
        // Parse scopes from the scope_str
        let scopes = match scope_str {
            Some(scopes) => match scopes.parse::<Scopes>() {
                Ok(scopes) => Some(scopes),
                Err(_) => {
                    warn!("Returned scope doesn't conform to docker scope style");
                    debug!("Returned scope: {:?}", scopes);
                    None
                }
            },
            None => {
                warn!("WWW-Authenticate header did not include a scope attribute");
                None
            }
        };
        // update the scope cache appropriately
        match scopes {
            None => {
                // This happens when the server isn't using docker-style scopes, or didn't
                // return any scopes at all (e.g: when using Basic auth)
                // In this case, use the expected scope as the cache entry directly.
                *headers = auth_headers.clone()
            }
            Some(mut scopes) => {
                // Check that expected scopes used in containrs line up with the returned
                // scopes. If they don't, it's not a critical mission failure, but it is a
                // cache miss which could have been avoided.
                //
                // TODO: Infer best-guess scopes from URLs.
                // if such a system were put in place, this wouldn't be an warning. Instead, the
                // system would dynamically update it's guess for the expected scope.
                if scopes.is_disjoint(expected_scope) {
                    warn!("The expected scope did not overlap with the server's returned scopes.");
                    debug!("Expected {:?}", expected_scope);
                    debug!("Returned {:?}", scopes);
                    // Add the expected scope to the scope list to get some caching
                    scopes.add(expected_scope.clone());
                }
                // Associate the auth_headers with whatever scopes they correspond to
                let mut scope_cache = self.scope_cache.lock().await;
                for scope in scopes {
                    if scope.resource() != expected_scope.resource() {
                        scope_cache.insert(scope, auth_headers.clone());
                    } else {
                        *headers = auth_headers.clone()
                    }
                }
            }
        }
        Ok(headers.clone())
    }
async fn authenticate < U : IntoUrl > ( & self , method : Method , url : U , expected_scope : & Scope , ) -> Result < HeaderMap > { // since multiple requests can be fired off at the same time, special care has // to be taken when accessing / updating the authentication headers cache to // prevent multiple threads from performing authentication requests for the same // scope (wasting time and bandwidth). // first, acquire a lock on the entire cache let mut scope_cache = self . scope_cache . lock ( ) . await ; // check if the expected_scope already exists in the cache if let Some ( headers ) = scope_cache . get ( expected_scope ) { // if it exists, that means the scope is either already authenticated, or a // task is currently authenticating it. // // In the latter case, this task should block until the authentication flow is // completed, _without_ blocking other tasks from querying the cache. As such, // the outer cache lock is dropped prior to waiting on the inner lock. std :: mem :: drop ( scope_cache ) ; let headers = headers . lock ( ) . await ; // TODO: store expiration time alongside scope, checking for expiry return Ok ( headers . clone ( ) ) ; } ; // alright, look like this task gets to authenticate this request debug ! ( " " expected_scope ) ; // insert a new entry into the scope cache, and immediately acquire it's inner // lock. // // This call to .lock() will not block, as this task holds the outer cache lock, // preventing any other tasks from calling .get() in the brief period of // time between insertion and lock acquisition. scope_cache . insert ( expected_scope . clone ( ) , HeaderMap :: new ( ) ) ; let headers = scope_cache . get ( expected_scope ) . expect ( " " ) ; let mut headers = headers . lock ( ) . await ; std :: mem :: drop ( scope_cache ) ; // once auth flow is completed, this value will be updated, both the inner mutex // will automatically release. // perform unauthenticated request to see what sort of authorization is required let unauth_res = self . client . request ( method , url ) . send ( ) . await . context ( AuthError :: EndpointNoResponse ) ? ; trace ! ( " " unauth_res ) ; if unauth_res . status ( ) != StatusCode :: UNAUTHORIZED { // that's weird, but okay. Just pass up an empty auth header warn ! ( " " ) ; return Ok ( HeaderMap :: new ( ) ) ; } let challenge = extract_challenge_from_headers ( unauth_res . headers ( ) ) ? ; let scope_str = challenge . parameters ( ) . get ( " " ) . cloned ( ) ; let auth_headers = match challenge . scheme ( ) { ChallengeScheme :: Bearer => self . authenticate_bearer ( expected_scope , challenge ) . await ? , _ => unreachable ! ( ) , } ; // Parse scopes from the scope_str let scopes = match scope_str { Some ( scopes ) => match scopes . parse :: < Scopes > ( ) { Ok ( scopes ) => Some ( scopes ) , Err ( _ ) => { warn ! ( " " ) ; debug ! ( " " scopes ) ; None } } , None => { warn ! ( " " ) ; None } } ; // update the scope cache appropriately match scopes { None => { // This happens when the server isn't using docker-style scopes, or didn't // return any scopes at all (e.g: when using Basic auth) // In this case, use the expected scope as the cache entry directly. * headers = auth_headers . clone ( ) } Some ( mut scopes ) => { // Check that expected scopes used in containrs line up with the returned // scopes. If they don't, it's not a critical mission failure, but it is a // cache miss which could have been avoided. // // TODO: Infer best-guess scopes from URLs. // if such a system were put in place, this wouldn't be an warning. Instead, the // system would dynamically update it's guess for the expected scope. if scopes . is_disjoint ( expected_scope ) { warn ! ( " " ) ; debug ! ( " " expected_scope ) ; debug ! ( " " scopes ) ; // Add the expected scope to the scope list to get some caching scopes . add ( expected_scope . clone ( ) ) ; } // Associate the auth_headers with whatever scopes they correspond to let mut scope_cache = self . scope_cache . lock ( ) . await ; for scope in scopes { if scope . resource ( ) != expected_scope . resource ( ) { scope_cache . insert ( scope , auth_headers . clone ( ) ) ; } else { * headers = auth_headers . clone ( ) } } } } Ok ( headers . clone ( ) ) }
Perform authentication flow for the given request, stashing the
resulting authentication headers in the cache.
Perform authentication flow for the given request stashing the resulting authentication headers in the cache .
&self
method
Method
url
U
expected_scope
&Scope
&self
method
Method
url
U
expected_scope
&Scope