Initial OAuth2 (Google variant) support for dav storage

This commits adds 'oauth2_google' authentication mechanism to dav
storage driver.
This commit is contained in:
Marek Marczykowski 2016-03-26 00:08:33 +01:00 committed by Markus Unterwaditzer
parent 4cb7e6c1f5
commit 6d8db949fa
4 changed files with 95 additions and 5 deletions

View file

@ -207,5 +207,28 @@ Vdirsyncer is continuously tested against the latest version of Baikal_.
Google
------
Vdirsyncer doesn't currently support Google accounts fully. For possible
solutions see :gh:`202` and :gh:`8`.
Using vdirsyncer with Google Calendar is possible, but it is not tested
frequently.
::
[storage cal]
type = caldav
url = https://apidata.googleusercontent.com/caldav/v2/
auth = oauth2_google
[storage card]
type = carddav
url = https://www.googleapis.com/carddav/v1/principals/EMAIL/lists/default
auth = oauth2_google
At first run you will be asked to authorize application for google account
access. Simply follow the instructions. You'll be asked to modify configuration
file (save `refresh_token` as a password).
- Google's CardDav implementation is very limited, may lead to data loss, use
with care.
- You can select which calendars to sync on
`CalDav settings page <https://calendar.google.com/calendar/syncselect>`_
For more information see :gh:`202` and :gh:`8`.

View file

@ -80,7 +80,8 @@ setup(
},
install_requires=requirements,
extras_require={
'remotestorage': ['requests-oauthlib']
'remotestorage': ['requests-oauthlib'],
'oauth2': ['requests-oauthlib'],
},
cmdclass={
'minimal_requirements': PrintRequirements

View file

@ -3,6 +3,8 @@
import datetime
import logging
import click
from lxml import etree
import requests
@ -14,6 +16,15 @@ from .http import HTTP_STORAGE_PARAMETERS, USERAGENT, prepare_auth, \
from .. import exceptions, utils
from ..utils.compat import text_type, to_native
OAUTH2_GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
OAUTH2_GOOGLE_REFRESH_URL = 'https://www.googleapis.com/oauth2/v4/token'
have_oauth2 = True
try:
from requests_oauthlib import OAuth2Session
oauth2_client_id = 'FIXME'
oauth2_client_secret = 'FIXME'
except ImportError:
have_oauth2 = False
dav_logger = logging.getLogger(__name__)
@ -301,7 +312,6 @@ class DavSession(object):
useragent=USERAGENT, verify_fingerprint=None,
auth_cert=None):
self._settings = {
'auth': prepare_auth(auth, username, password),
'cert': prepare_client_cert(auth_cert),
}
self._settings.update(prepare_verify(verify, verify_fingerprint))
@ -310,6 +320,21 @@ class DavSession(object):
self.url = url.rstrip('/') + '/'
self.parsed_url = utils.compat.urlparse.urlparse(self.url)
self._session = None
self._token = None
self._use_oauth2_google = False
if auth == 'oauth2_google':
if not have_oauth2:
raise exceptions.UserError("requests-oauthlib not installed")
if password:
self._token = {
'refresh_token': password,
# Will be derived from refresh_token
'access_token': 'dummy',
'expires_in': -30
}
self._use_oauth2_google = True
else:
self._settings['auth'] = prepare_auth(auth, username, password)
def request(self, method, path, **kwargs):
url = self.url
@ -317,6 +342,44 @@ class DavSession(object):
url = utils.compat.urlparse.urljoin(self.url, path)
if self._session is None:
self._session = requests.session()
if self._use_oauth2_google:
self._session = OAuth2Session(
client_id=oauth2_client_id,
token=self._token,
redirect_uri='urn:ietf:wg:oauth:2.0:oob',
scope=['https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/carddav'],
auto_refresh_url=OAUTH2_GOOGLE_REFRESH_URL,
auto_refresh_kwargs={
'client_id': oauth2_client_id,
'client_secret': oauth2_client_secret,
},
token_updater=lambda x: None
)
if not self._token:
authorization_url, state = self._session.authorization_url(
OAUTH2_GOOGLE_TOKEN_URL,
# access_type and approval_prompt are Google specific
# extra parameters.
access_type="offline", approval_prompt="force")
click.echo('Opening {} ...'.format(authorization_url))
try:
utils.open_graphical_browser(authorization_url)
except Exception as e:
dav_logger.warning(str(e))
click.echo("Follow the instructions on the page.")
code = click.prompt("Paste obtained code")
self._token = self._session.fetch_token(
OAUTH2_GOOGLE_REFRESH_URL,
code=code,
# Google specific extra parameter used for client
# authentication
client_secret=oauth2_client_secret,
)
raise exceptions.UserError(
"Set the following token in a password field: {}".
format(self._token['refresh_token']))
more = dict(self._settings)
more.update(kwargs)

View file

@ -82,7 +82,10 @@ HTTP_STORAGE_PARAMETERS = '''
information.
:param auth: Optional. Either ``basic``, ``digest`` or ``guess``. Default
``guess``. If you know yours, consider setting it explicitly for
performance.
performance. For caldav and carddav, additionaly ``oauth2_google`` is
supported. ``password`` setting should point a file for OAuth2 token
storage (directory must already exists, but file itself will be created
automatically).
:param auth_cert: Optional. Either a path to a certificate with a client
certificate and the key or a list of paths to the files with them.
:param useragent: Default ``vdirsyncer``.