Пример #1
0
def _make_github_repos(
        gh, github_login, github_passwd, github_organization, rinfo, existing,
        access_protocol, dryrun):
    cred = UserPassword('github', 'https://github.com/login')

    # determine the entity under which to create the repos
    entity = _get_github_entity(
        gh,
        cred,
        github_login,
        github_passwd,
        github_organization)

    res = []
    for ds, reponame in rinfo:
        try:
            access_url, existed = _make_github_repo(
                gh,
                entity,
                reponame,
                existing,
                access_protocol,
                dryrun)
            res.append((ds, access_url, existed))
        except gh.BadCredentialsException as e:
            # things blew up, wipe out cred store, if anything is in it
            if cred.is_known:
                cred.delete()
            raise e
    return res
Пример #2
0
def _make_github_repos(gh, github_login, github_passwd, github_organization,
                       rinfo, existing, access_protocol, dryrun):
    # make it per user if github_login was provided. People might want to use
    # different credentials etc
    cred_identity = "%s@github" % github_login if github_login else "github"
    cred = UserPassword(cred_identity, 'https://github.com/login')

    # determine the entity under which to create the repos
    entity = _get_github_entity(gh, cred, github_login, github_passwd,
                                github_organization)

    res = []
    for ds, reponame in rinfo:
        try:
            access_url, existed = _make_github_repo(gh, github_login, entity,
                                                    reponame, existing,
                                                    access_protocol, dryrun)
            res.append((ds, access_url, existed))
        except gh.BadCredentialsException as e:
            # things blew up, wipe out cred store, if anything is in it
            if cred.is_known:
                # to avoid surprises "who ate my creds?", warn the user
                lgr.warning(
                    "Authentication failed, deleting stored credential %s",
                    cred_identity)
                cred.delete()
            raise e
    return res
Пример #3
0
def _make_github_repos(
        gh, github_user, github_passwd, github_organization, rinfo, existing,
        access_protocol, dryrun):
    cred = UserPassword('github', 'https://github.com/login')

    # determine the entity under which to create the repos
    entity = _get_github_entity(
        gh,
        cred,
        github_user,
        github_passwd,
        github_organization)

    res = []
    for ds, reponame in rinfo:
        try:
            access_url, existed = _make_github_repo(
                gh,
                entity,
                reponame,
                existing,
                access_protocol,
                dryrun)
            res.append((ds, access_url, existed))
        except gh.BadCredentialsException as e:
            # things blew up, wipe out cred store, if anything is in it
            if cred.is_known:
                cred.delete()
            raise e
    return res
Пример #4
0
def _make_github_repos(
        gh, github_login, github_passwd, github_organization, rinfo, existing,
        access_protocol, dryrun):
    # make it per user if github_login was provided. People might want to use
    # different credentials etc
    cred_identity = "%s@github" % github_login if github_login else "github"
    cred = UserPassword(cred_identity, 'https://github.com/login')

    # determine the entity under which to create the repos
    entity = _get_github_entity(
        gh,
        cred,
        github_login,
        github_passwd,
        github_organization)

    res = []
    for ds, reponame in rinfo:
        try:
            access_url, existed = _make_github_repo(
                gh,
                github_login,
                entity,
                reponame,
                existing,
                access_protocol,
                dryrun)
            res.append((ds, access_url, existed))
        except gh.BadCredentialsException as e:
            # things blew up, wipe out cred store, if anything is in it
            if cred.is_known:
                # to avoid surprises "who ate my creds?", warn the user
                lgr.warning(
                    "Authentication failed, deleting stored credential %s",
                    cred_identity
                )
                cred.delete()
            raise e
    return res
Пример #5
0
def get_credentials(allow_interactive=True):
    # prefer the environment
    if 'OSF_TOKEN' in environ or all(
            k in environ for k in ('OSF_USERNAME', 'OSF_PASSWORD')):
        return dict(
            token=environ.get('OSF_TOKEN', None),
            username=environ.get('OSF_USERNAME', None),
            password=environ.get('OSF_PASSWORD', None),
        )

    # fall back on DataLad credential manager
    token_auth = Token(
        name='https://osf.io',
        url='https://osf.io/settings/tokens',
    )
    up_auth = UserPassword(
        name='https://osf.io',
        url='https://osf.io/settings/account',
    )

    do_interactive = allow_interactive and ui.is_interactive()

    # get auth token, from environment, or from datalad credential store
    # if known-- we do not support first-time entry during a test run
    token = environ.get(
        'OSF_TOKEN',
        token_auth().get('token', None)
        if do_interactive or token_auth.is_known
        else None)
    username = None
    password = None
    if not token:
        # now same for user/password if there was no token
        username = environ.get(
            'OSF_USERNAME',
            up_auth().get('user', None)
            if do_interactive or up_auth.is_known
            else None)
        password = environ.get(
            'OSF_PASSWORD',
            up_auth().get('password', None)
            if do_interactive or up_auth.is_known
            else None)

    return dict(token=token, username=username, password=password)
Пример #6
0
def setup_credentials():
    # check if anything need to be done still
    if 'OSF_TOKEN' in environ or all(
            k in environ for k in ('OSF_USERNAME', 'OSF_PASSWORD')):
        return dict(
            token=environ.get('OSF_TOKEN', None),
            username=environ.get('OSF_USERNAME', None),
            password=environ.get('OSF_USERNAME', None),
        )

    token_auth = Token(name='https://osf.io', url=None)
    up_auth = UserPassword(name='https://osf.io', url=None)

    # get auth token, form environment, or from datalad credential store
    # if known-- we do not support first-time entry during a test run
    token = environ.get(
        'OSF_TOKEN',
        token_auth().get('token', None) if token_auth.is_known else None)
    username = None
    password = None
    if not token:
        # now same for user/password if there was no token
        username = environ.get(
            'OSF_USERNAME',
            up_auth().get('user', None) if up_auth.is_known else None)
        password = environ.get(
            'OSF_PASSWORD',
            up_auth().get('password', None) if up_auth.is_known else None)

    # place into environment, for now this is the only way the special remote
    # can be supplied with credentials
    for k, v in (('OSF_TOKEN', token), ('OSF_USERNAME', username),
                 ('OSF_PASSWORD', password)):
        if v:
            environ[k] = v
    return dict(token=token, username=username, password=password)
Пример #7
0
def test_datalad_credential_helper(path=None):

    ds = Dataset(path).create()

    # tell git to use git-credential-datalad
    ds.config.add('credential.helper', 'datalad', scope='local')
    ds.config.add('datalad.credentials.githelper.noninteractive',
                  'true',
                  scope='global')

    from datalad.downloaders.providers import Providers

    url1 = "https://datalad-test.org/some"
    url2 = "https://datalad-test.org/other"
    provider_name = "datalad-test.org"

    # `Providers` code is old and only considers a dataset root based on PWD
    # for config lookup. contextmanager below can be removed once the
    # provider/credential system is redesigned.
    with chpwd(ds.path):

        gitcred = GitCredentialInterface(url=url1, repo=ds)

        # There's nothing set up yet, helper should return empty
        gitcred.fill()
        eq_(gitcred['username'], '')
        eq_(gitcred['password'], '')

        # store new credentials
        # Note, that `Providers.enter_new()` currently uses user-level config
        # files for storage only. TODO: make that an option!
        # To not mess with existing ones, fail if it already exists:

        cfg_file = Path(Providers._get_providers_dirs()['user']) \
                   / f"{provider_name}.cfg"
        assert_false(cfg_file.exists())

        # Make sure we clean up
        from datalad.tests import _TEMP_PATHS_GENERATED
        _TEMP_PATHS_GENERATED.append(str(cfg_file))

        # Give credentials to git and ask it to store them:
        gitcred = GitCredentialInterface(url=url1,
                                         username="******",
                                         password="******",
                                         repo=ds)
        gitcred.approve()

        assert_true(cfg_file.exists())
        providers = Providers.from_config_files()
        p1 = providers.get_provider(url=url1, only_nondefault=True)
        assert_is_instance(p1.credential, UserPassword)
        eq_(p1.credential.get('user'), 'dl-user')
        eq_(p1.credential.get('password'), 'dl-pwd')

        # default regex should be host only, so matching url2, too
        p2 = providers.get_provider(url=url2, only_nondefault=True)
        assert_is_instance(p1.credential, UserPassword)
        eq_(p1.credential.get('user'), 'dl-user')
        eq_(p1.credential.get('password'), 'dl-pwd')

        # git, too, should now find it for both URLs
        gitcred = GitCredentialInterface(url=url1, repo=ds)
        gitcred.fill()
        eq_(gitcred['username'], 'dl-user')
        eq_(gitcred['password'], 'dl-pwd')

        gitcred = GitCredentialInterface(url=url2, repo=ds)
        gitcred.fill()
        eq_(gitcred['username'], 'dl-user')
        eq_(gitcred['password'], 'dl-pwd')

        # Rejection must not currently lead to deleting anything, since we would
        # delete too broadly.
        gitcred.reject()
        assert_true(cfg_file.exists())
        gitcred = GitCredentialInterface(url=url1, repo=ds)
        gitcred.fill()
        eq_(gitcred['username'], 'dl-user')
        eq_(gitcred['password'], 'dl-pwd')
        dlcred = UserPassword(name=provider_name)
        eq_(dlcred.get('user'), 'dl-user')
        eq_(dlcred.get('password'), 'dl-pwd')
Пример #8
0
def _enable_remote(ds, name, known_remotes, url, pushurl, fetch, description,
                   as_common_datasrc, publish_depends, publish_by_default,
                   annex_wanted, annex_required, annex_group,
                   annex_groupwanted, inherit, get_annex_info, **res_kwargs):
    result_props = dict(action='enable-sibling',
                        path=ds.path,
                        type='sibling',
                        name=name,
                        **res_kwargs)

    if not isinstance(ds.repo, AnnexRepo):
        yield dict(result_props,
                   status='impossible',
                   message='cannot enable sibling of non-annex dataset')
        return

    if name is None:
        yield dict(result_props,
                   status='error',
                   message='require `name` of sibling to enable')
        return

    # get info on special remote
    sp_remotes = {
        v['name']: dict(v, uuid=k)
        for k, v in ds.repo.get_special_remotes().items()
    }
    remote_info = sp_remotes.get(name, None)

    if remote_info is None:
        yield dict(result_props,
                   status='impossible',
                   message=("cannot enable sibling '%s', not known", name))
        return

    env = None
    cred = None
    if remote_info.get('type', None) == 'webdav':
        # a webdav special remote -> we need to supply a username and password
        if not ('WEBDAV_USERNAME' in os.environ
                and 'WEBDAV_PASSWORD' in os.environ):
            # nothing user-supplied
            # let's consult the credential store
            hostname = urlparse(remote_info.get('url', '')).netloc
            if not hostname:
                yield dict(
                    result_props,
                    status='impossible',
                    message=
                    "cannot determine remote host, credential lookup for webdav access is not possible, and not credentials were supplied"
                )
            cred = UserPassword('webdav:{}'.format(hostname))
            if not cred.is_known:
                try:
                    cred.enter_new(
                        instructions=
                        "Enter credentials for authentication with WEBDAV server at {}"
                        .format(hostname),
                        user=os.environ.get('WEBDAV_USERNAME', None),
                        password=os.environ.get('WEBDAV_PASSWORD', None))
                except KeyboardInterrupt:
                    # user hit Ctrl-C
                    yield dict(
                        result_props,
                        status='impossible',
                        message=
                        "credentials are required for sibling access, abort")
                    return
            creds = cred()
            # update the env with the two necessary variable
            # we need to pass a complete env because of #1776
            env = dict(os.environ,
                       WEBDAV_USERNAME=creds['user'],
                       WEBDAV_PASSWORD=creds['password'])

    try:
        ds.repo.enable_remote(name, env=env)
        result_props['status'] = 'ok'
    except AccessDeniedError as e:
        # credentials are wrong, wipe them out
        if cred and cred.is_known:
            cred.delete()
        result_props['status'] = 'error'
        result_props['message'] = e.message
    except AccessFailedError as e:
        # some kind of connection issue
        result_props['status'] = 'error'
        result_props['message'] = e.message
    except Exception as e:
        # something unexpected
        raise e

    yield result_props
Пример #9
0
    def __call__(method="token", reset=False):
        auth = None
        cred_spec = []
        if method == 'token':
            cred_spec = dict(token='token')
            auth = Token(
                name='https://osf.io',
                url='https://osf.io/settings/tokens',
            )
        elif method == 'userpassword':
            cred_spec = dict(user='******', password='******')
            auth = UserPassword(
                name='https://osf.io',
                url='https://osf.io/settings/account',
            )
        else:
            raise ValueError(
                'Unknown authentication method: {}'.format(method))
        if reset and auth.is_known:
            auth.delete()
        cred = {v: auth().get(k, None) for k, v in cred_spec.items()}

        # now verify that the credentials work by querying the
        # logged in user
        osf = OSF(**cred)
        try:
            req = osf.session.get('https://api.osf.io/v2/users/me/')
            req.raise_for_status()
        except UnauthorizedException:
            auth.delete()
            yield dict(
                action='osf_credentials',
                status='error',
                message='Invalid credentials',
                path=None,
            )
            return
        except Exception as e:
            yield dict(
                action='osf_credentials',
                status='impossible',
                message='Could not verify credentials, '
                'please try again: {}'.format(exc_str(e)),
                # needed to pacify DataLad 0.13.0 and earlier
                path=None,
            )
            return
        # if we get here auth has worked fine
        # get some attributes for an informative message
        attrs = req.json().get('data', {}).get('attributes', {})
        yield dict(
            action='osf_credentials',
            status='ok',
            message='authenticated{}{}{}'.format(
                ' as ' if any(
                    attrs.get(k, None)
                    for k in ('email', 'full_name')) else '',
                attrs.get('full_name', ''), ' <{}>'.format(attrs['email'])
                if attrs.get('email', None) else ''),
            # needed to pacify DataLad 0.13.0 and earlier
            path=None,
            # report effective credentials
            **cred,
        )
Пример #10
0
    def __call__(url,
                 path="{subject}/{session}/{scan}/",
                 project=None,
                 force=False,
                 dataset=None):
        from pyxnat import Interface as XNATInterface

        ds = require_dataset(dataset,
                             check_installed=True,
                             purpose='initialization')

        config = ds.config
        path = with_pathsep(path)

        # prep for yield
        res = dict(
            action='xnat_init',
            path=ds.path,
            type='dataset',
            logger=lgr,
            refds=ds.path,
        )

        # obtain user credentials, use simplified/stripped URL as identifier
        # given we don't have more knowledge than the user, do not
        # give a `url` to provide hints on how to obtain credentials
        parsed_url = urlparse(url)
        no_proto_url = '{}{}'.format(parsed_url.netloc,
                                     parsed_url.path).replace(' ', '')
        cred = UserPassword(name=no_proto_url, url=None)()

        xn = XNATInterface(server=url, **cred)

        # now we make a simple request to obtain the server version
        # we don't care much, but if the URL or the credentials are wrong
        # we will not get to see one
        try:
            xnat_version = xn.version()
            lgr.debug("XNAT server version is %s", xnat_version)
        except Exception as e:
            yield dict(
                res,
                status='error',
                message=('Failed to access the XNAT server. Full error:\n%s',
                         e),
            )
            return

        if project is None:
            from datalad.ui import ui
            projects = xn.select.projects().get()
            ui.message('No project name specified. The following projects are '
                       'available on {} for user {}:'.format(
                           url, cred['user']))
            for p in sorted(projects):
                # list and prep for C&P
                # TODO multi-column formatting?
                ui.message("  {}".format(quote_cmdlinearg(p)))
            return

        # query the specified project to make sure it exists and is accessible
        proj = xn.select.project(project)

        try:
            nsubj = len(proj.subjects().get())
        except Exception as e:
            yield dict(
                res,
                status='error',
                message=(
                    'Failed to obtain information on project %s from XNAT. '
                    'Full error:\n%s', project, e),
            )
            return

        lgr.info('XNAT reports %i subjects currently on-record for project %s',
                 nsubj, project)

        # check if dataset already initialized
        auth_dir = ds.pathobj / '.datalad' / 'providers'
        if auth_dir.exists() and not force:
            yield dict(
                res,
                status='error',
                message='Dataset found already initialized, '
                'use `force` to reinitialize',
            )
            return

        # put essential configuration into the dataset
        config.set('datalad.xnat.default.url',
                   url,
                   where='dataset',
                   reload=False)
        config.set('datalad.xnat.default.project', project, where='dataset')
        config.set('datalad.xnat.default.path', path, where='dataset')

        ds.save(
            path=ds.pathobj / '.datalad' / 'config',
            to_git=True,
            message="Configure default XNAT url and project",
        )

        # Configure XNAT access authentication
        ds.run_procedure(spec='cfg_xnat_dataset')

        yield dict(
            res,
            status='ok',
        )
        return
Пример #11
0
def _enable_remote(
        ds, name, known_remotes, url, pushurl, fetch, description,
        as_common_datasrc, publish_depends, publish_by_default,
        annex_wanted, annex_required, annex_group, annex_groupwanted,
        inherit, get_annex_info,
        **res_kwargs):
    result_props = dict(
        action='enable-sibling',
        path=ds.path,
        type='sibling',
        name=name,
        **res_kwargs)

    if not isinstance(ds.repo, AnnexRepo):
        yield dict(
            result_props,
            status='impossible',
            message='cannot enable sibling of non-annex dataset')
        return

    if name is None:
        yield dict(
            result_props,
            status='error',
            message='require `name` of sibling to enable')
        return

    # get info on special remote
    sp_remotes = {v['name']: dict(v, uuid=k) for k, v in ds.repo.get_special_remotes().items()}
    remote_info = sp_remotes.get(name, None)

    if remote_info is None:
        yield dict(
            result_props,
            status='impossible',
            message=("cannot enable sibling '%s', not known", name))
        return

    env = None
    cred = None
    if remote_info.get('type', None) == 'webdav':
        # a webdav special remote -> we need to supply a username and password
        if not ('WEBDAV_USERNAME' in os.environ and 'WEBDAV_PASSWORD' in os.environ):
            # nothing user-supplied
            # let's consult the credential store
            hostname = urlparse(remote_info.get('url', '')).netloc
            if not hostname:
                yield dict(
                    result_props,
                    status='impossible',
                    message="cannot determine remote host, credential lookup for webdav access is not possible, and not credentials were supplied")
            cred = UserPassword('webdav:{}'.format(hostname))
            if not cred.is_known:
                try:
                    cred.enter_new(
                        instructions="Enter credentials for authentication with WEBDAV server at {}".format(hostname),
                        user=os.environ.get('WEBDAV_USERNAME', None),
                        password=os.environ.get('WEBDAV_PASSWORD', None))
                except KeyboardInterrupt:
                    # user hit Ctrl-C
                    yield dict(
                        result_props,
                        status='impossible',
                        message="credentials are required for sibling access, abort")
                    return
            creds = cred()
            # update the env with the two necessary variable
            # we need to pass a complete env because of #1776
            env = dict(
                os.environ,
                WEBDAV_USERNAME=creds['user'],
                WEBDAV_PASSWORD=creds['password'])

    try:
        ds.repo.enable_remote(name, env=env)
        result_props['status'] = 'ok'
    except AccessDeniedError as e:
        # credentials are wrong, wipe them out
        if cred and cred.is_known:
            cred.delete()
        result_props['status'] = 'error'
        result_props['message'] = str(e)
    except AccessFailedError as e:
        # some kind of connection issue
        result_props['status'] = 'error'
        result_props['message'] = str(e)
    except Exception as e:
        # something unexpected
        raise e

    yield result_props
Пример #12
0
    def __call__(subjects='list', dataset=None, ifexists=None, force=False):
        from pyxnat import Interface as XNATInterface

        ds = require_dataset(dataset, check_installed=True, purpose='update')

        subjects = ensure_list(subjects)

        # require a clean dataset
        if ds.repo.dirty:
            yield get_status_dict(
                'update',
                ds=ds,
                status='impossible',
                message=(
                    'Clean dataset required; use `datalad status` to inspect '
                    'unsaved changes'))
            return

        # prep for yield
        res = dict(
            action='xnat_update',
            path=ds.path,
            type='dataset',
            logger=lgr,
            refds=ds.path,
        )
        # obtain configured XNAT url and project name
        xnat_cfg_name = ds.config.get('datalad.xnat.default-name', 'default')
        cfg_section = 'datalad.xnat.{}'.format(xnat_cfg_name)
        xnat_url = ds.config.get('{}.url'.format(cfg_section))
        xnat_project = ds.config.get('{}.project'.format(cfg_section))
        file_path = ds.config.get('{}.path'.format(cfg_section))

        # obtain user credentials
        parsed_url = urlparse(xnat_url)
        no_proto_url = '{}{}'.format(parsed_url.netloc,
                                     parsed_url.path).replace(' ', '')
        cred = UserPassword(name=no_proto_url, url=None)()
        xn = XNATInterface(server=xnat_url, **cred)

        # provide subject list
        if 'list' in subjects:
            from datalad.ui import ui
            subs = xn.select.project(xnat_project).subjects().get()
            ui.message('The following subjects are available for XNAT '
                       'project {}:'.format(xnat_project))
            for s in sorted(subs):
                ui.message(" {}".format(quote_cmdlinearg(s)))
            ui.message(
                'Specify a specific subject(s) or "all" to download associated '
                'files for.')
            return

        # query the specified subject(s) to make sure it exists and is accessible
        if 'all' not in subjects:
            from datalad.ui import ui
            subs = []
            for s in subjects:
                sub = xn.select.project(xnat_project).subject(s)
                nexp = len(sub.experiments().get())
                if nexp > 0:
                    subs.append(s)
                else:
                    ui.message(
                        'Failed to obtain information on subject {} from XNAT '
                        'project {}:'.format(s, xnat_project))
                    return
        else:
            # if all, get list of all subjects
            subs = xn.select.project(xnat_project).subjects().get()

        # parse and download one subject at a time
        from datalad_xnat.parser import parse_xnat
        addurl_dir = ds.pathobj / 'code' / 'addurl_files'
        for sub in subs:
            yield from parse_xnat(
                ds,
                sub=sub,
                force=force,
                xn=xn,
                xnat_url=xnat_url,
                xnat_project=xnat_project,
            )

            # add file urls for subject
            lgr.info('Downloading files for subject %s', sub)
            table = f"{addurl_dir}/{sub}_table.csv"
            # this corresponds to the header field 'filename' in the csv table
            filename = '{filename}'
            filenameformat = f"{file_path}{filename}"
            ds.addurls(
                table,
                '{url}',
                filenameformat,
                ifexists=ifexists,
                save=False,
                cfg_proc='xnat_dataset',
                result_renderer='default',
            )

            ds.save(message=f"Update files for subject {sub}", recursive=True)

        lgr.info(
            'Files were updated for the following subjects in XNAT project %s:',
            xnat_project)
        for s in sorted(subs):
            lgr.info(" {}".format(quote_cmdlinearg(s)))

        yield dict(res, status='ok')
        return
Пример #13
0
 def _get_github_cred():
     """Trimmed down helper"""
     return UserPassword("github", "does not matter")