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:
parent
096b7e61aa
commit
905ca53824
3 changed files with 44 additions and 12 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue