WebDAV: send Basic Auth via http in some cases (FDO #57248)

It turned out that finding databases on an Apple Calendar server accessed via
http depends on sending Basic Auth even when the server does not ask for it:
without authentication, there is no information about the current principal,
which is necessary for finding the user's databases.

To make this work again, sending the authentication header is now forced for
plain http if (and only if) the request which should have returned the
principal URL fails to include it. This implies sending the same request
twice, but as this scenario should be rare in practise (was only done for
testing), this is acceptable.
This commit is contained in:
Patrick Ohly 2015-03-03 00:24:08 -08:00
parent 096b7e61aa
commit 905ca53824
3 changed files with 44 additions and 12 deletions

View File

@ -189,7 +189,7 @@ std::string Status2String(const ne_status *status)
}
Session::Session(const boost::shared_ptr<Settings> &settings) :
m_forceAuthorizationOnce(false),
m_forceAuthorizationOnce(AUTH_ON_DEMAND),
m_credentialsSent(false),
m_oauthTokenRejections(0),
m_settings(settings),
@ -324,9 +324,10 @@ int Session::getCredentials(void *userdata, const char *realm, int attempt, char
}
}
void Session::forceAuthorization(const boost::shared_ptr<AuthProvider> &authProvider)
void Session::forceAuthorization(ForceAuthorization forceAuthorization,
const boost::shared_ptr<AuthProvider> &authProvider)
{
m_forceAuthorizationOnce = true;
m_forceAuthorizationOnce = forceAuthorization;
m_authProvider = authProvider;
}
@ -355,8 +356,9 @@ void Session::preSend(ne_request *req, ne_buffer *header)
// Only do this once when using normal username/password.
// Always do it when using OAuth2.
bool useOAuth2 = m_authProvider && m_authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2);
if (m_forceAuthorizationOnce || useOAuth2) {
m_forceAuthorizationOnce = false;
bool forceAlways = m_forceAuthorizationOnce == AUTH_ALWAYS;
if (m_forceAuthorizationOnce != AUTH_ON_DEMAND || useOAuth2) {
m_forceAuthorizationOnce = AUTH_ON_DEMAND;
bool haveAuthorizationHeader = boost::starts_with(header->data, "Authorization:") ||
strstr(header->data, "\nAuthorization:");
@ -369,7 +371,7 @@ void Session::preSend(ne_request *req, ne_buffer *header)
m_credentialsSent = true;
// SmartPtr<char *> blob(ne_base64((const unsigned char *)m_oauth2Bearer.c_str(), m_oauth2Bearer.size()));
ne_buffer_concat(header, "Authorization: Bearer ", m_oauth2Bearer.c_str() /* blob.get() */, "\r\n", (const char *)NULL);
} else if (m_uri.m_scheme == "https") {
} else if (forceAlways || m_uri.m_scheme == "https") {
// append "Authorization: Basic" header if not present already
if (!haveAuthorizationHeader) {
Credentials creds = m_authProvider->getCredentials();

View File

@ -251,13 +251,21 @@ std::string Status2String(const ne_status *status);
* Throws transport errors for fatal problems.
*/
class Session {
public:
enum ForceAuthorization {
AUTH_ON_DEMAND,
AUTH_HTTPS,
AUTH_ALWAYS
};
private:
/**
* @param settings must provide information about settings on demand
*/
Session(const boost::shared_ptr<Settings> &settings);
static boost::shared_ptr<Session> m_cachedSession;
bool m_forceAuthorizationOnce;
ForceAuthorization m_forceAuthorizationOnce;
boost::shared_ptr<AuthProvider> m_authProvider;
/**
@ -382,7 +390,7 @@ class Session {
* (when username/password are provided by AuthProvider) or all
* requests to use OAuth2 authentication.
*/
void forceAuthorization(const boost::shared_ptr<AuthProvider> &authProvider);
void forceAuthorization(ForceAuthorization forceAuthorization, const boost::shared_ptr<AuthProvider> &authProvider);
private:
boost::shared_ptr<Settings> m_settings;

View File

@ -620,7 +620,7 @@ void WebDAVSource::contactServer()
m_session = Neon::Session::create(m_settings);
SE_LOG_INFO(getDisplayName(), "using configured database=%s", database.c_str());
// force authentication via username/password or OAuth2
m_session->forceAuthorization(m_settings->getAuthProvider());
m_session->forceAuthorization(Neon::Session::AUTH_HTTPS, m_settings->getAuthProvider());
return;
}
@ -684,6 +684,7 @@ class Candidate {
public:
enum Flags {
LIST = (1u << 0), // Also list all members to find more candidates.
FORCE_AUTH = (1u << 1), // Force authentication for this candidate.
NONE = 0
};
@ -1063,7 +1064,7 @@ bool WebDAVSource::findCollections(const boost::function<bool (const std::string
// Use OAuth2, if available.
boost::shared_ptr<AuthProvider> authProvider = m_settings->getAuthProvider();
if (authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2)) {
m_session->forceAuthorization(authProvider);
m_session->forceAuthorization(Neon::Session::AUTH_HTTPS, authProvider);
}
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
@ -1102,7 +1103,10 @@ bool WebDAVSource::findCollections(const boost::function<bool (const std::string
// http://tools.ietf.org/html/rfc4918#appendix-E
// http://lists.w3.org/Archives/Public/w3c-dist-auth/2005OctDec/0243.html
// http://thread.gmane.org/gmane.comp.web.webdav.neon.general/717/focus=719
m_session->forceAuthorization(m_settings->getAuthProvider());
m_session->forceAuthorization((candidate.m_flags & Candidate::FORCE_AUTH) ?
Neon::Session::AUTH_ALWAYS : // we really mean it, do it also for http
Neon::Session::AUTH_HTTPS, // only when auth header is protected by https
m_settings->getAuthProvider());
davProps.clear();
// Avoid asking for CardDAV properties when only using CalDAV
// and vice versa, to avoid breaking both when the server is only
@ -1359,7 +1363,25 @@ bool WebDAVSource::findCollections(const boost::function<bool (const std::string
// TODO:
// xmlns:d="DAV:"
// <d:current-user-principal><d:href>/m8/carddav/principals/__uids__/patrick.ohly@googlemail.com/</d:href></d:current-user-principal>
if (tried.isNew(principal)) {
if (principal.m_uri.m_path.empty()) {
// Happens for example with Apple Calendar server and http:
// <current-user-principal>
// <unauthenticated/>
// </current-user-principal>
// We get the property, but it contains no href.
// Try again with authentication, even if not challenged to do so
// at the HTTP level.
// TODO (?): detect the <unauthenticated/> tag.
Candidate withAuth(candidate);
withAuth.m_flags |= Candidate::FORCE_AUTH;
bool retry = tried.isNew(withAuth);
SE_LOG_DEBUG(NULL, "empty current-user-prinicipal: %s",
retry ? "retry current URL with authentication" : "ignore it");
if (retry) {
next = withAuth;
}
} else if (tried.isNew(principal)) {
next = principal;
SE_LOG_DEBUG(NULL, "follow current-user-prinicipal to %s", next.m_uri.toURL().c_str());
}