Example #1
0
def registered():
    if not request.args['state'] == session.pop('state_token'):
        abort(403)
    data = {
        'client_id': app.config['ORCID_CLIENT_ID'],
        'client_secret': app.config['ORCID_CLIENT_SECRET'],
        'code': request.args['code'],
        'grant_type': 'authorization_code',
        'redirect_uri': url_for('registered', _external=True),
        'scope': '/authenticate',
    }
    response = requests.post(
        'https://pub.orcid.org/oauth/token',
        headers={'Accept': 'application/json'},
        allow_redirects=True, data=data)
    credentials = response.json()
    if not response.status_code == 200:
        app.logger.error('Response to request for ORCID credentials was not OK')
        app.logger.error('Request: %s', data)
        app.logger.error('Response: %s', response.text)
    identity = auth.add_user_or_update_credentials(credentials)
    database.get_db().commit()
    return make_response(
        """
        <!doctype html>
        <head>
            <script type="text/javascript">
            localStorage.auth = '{}';
            window.close();
            </script>
        </head>
        <body>
        """.format(json.dumps(
            {'name': credentials['name'], 'token': identity.b64token.decode()}
        )))
Example #2
0
def set_permissions(orcid, permissions=None):
    if permissions is None:
        permissions = []

    needs = set()

    with app.app_context():
        db = database.get_db()
        cursor = db.cursor()
        cursor.execute("select id from user where id=?", [orcid])

        user = cursor.fetchone()

        if user is None:
            raise ValueError('No user with orcid "{}" in database.'.format(orcid))

        for permission_name in permissions:
            permission_attr = "{}_permission".format(permission_name)
            permission = getattr(auth, permission_attr, None)

            if permission is None:
                raise ValueError("No such permission: {}".format(permission_name))

            for need in permission.needs:
                needs.add(tuple(need))

        cursor.execute("UPDATE user SET permissions = ? WHERE id = ?", [json.dumps(list(needs)), orcid])

        db.commit()
Example #3
0
    def put(self, id):
        permission = auth.UpdatePatchPermission(id)
        if not permission.can():
            raise auth.PermissionDenied(permission)
        try:
            patch = JsonPatch(parse_json(request))
            affected_entities = patching.validate(
                patch, database.get_dataset())
        except ResourceError as e:
            return e.response()
        except patching.InvalidPatchError as e:
            if str(e) != 'Could not apply JSON patch to dataset.':
                return {'status': 400, 'message': str(e)}, 400

        db = database.get_db()
        curs = db.cursor()
        curs.execute('''
UPDATE patch_request SET
original_patch = ?,
updated_entities = ?,
removed_entities = ?,
updated_by = ?
WHERE id = ?
        ''', (patch.to_string(),
              json.dumps(sorted(affected_entities['updated'])),
              json.dumps(sorted(affected_entities['removed'])),
              g.identity.id, id)
        )
        db.commit()
Example #4
0
def get_identity(b64token, cursor=None):
    if cursor is None:
        cursor = database.get_db().cursor()
    rows = cursor.execute('''
    SELECT
    user.id AS user_id,
    user.permissions AS user_permissions,
    patch_request.id AS patch_request_id,
    strftime("%s","now") > token_expires_at AS token_expired
    FROM user LEFT JOIN patch_request
    ON user.id = patch_request.created_by AND patch_request.open = 1
    WHERE user.b64token = ?''', (b64token,)).fetchall()
    if not rows:
        return UnauthenticatedIdentity(
            'invalid_token', 'The access token is invalid')
    if rows[0]['token_expired']:
        return UnauthenticatedIdentity(
            'invalid_token', 'The access token expired')
    identity = Identity(rows[0]['user_id'], auth_type='bearer')
    identity.b64token = b64token
    for p in json.loads(rows[0]['user_permissions']):
        identity.provides.add(tuple(p))
    for r in rows:
        if r['patch_request_id'] is not None:
            identity.provides.add(UpdatePatchNeed(value=r['patch_request_id']))
    return identity
 def setUp(self):
     self.db_fd, app.config['DATABASE'] = tempfile.mkstemp()
     app.config['TESTING'] = True
     commands.init_db()
     commands.load_data(filepath('test-data.json'))
     self.client = app.test_client()
     with open(filepath('test-patch-replace-values-1.json')) as f:
         self.patch = f.read()
     with app.app_context():
         self.unauthorized_identity = auth.add_user_or_update_credentials({
             'name': 'Dangerous Dan',
             'access_token': 'f7e00c02-6f97-4636-8499-037446d95446',
             'expires_in': 631138518,
             'orcid': '0000-0000-0000-000X',
         })
         db = database.get_db()
         curs = db.cursor()
         curs.execute('UPDATE user SET permissions = ? WHERE name = ?',
                      ('[]', 'Dangerous Dan'))
         self.user_identity = auth.add_user_or_update_credentials({
             'name': 'Regular Gal',
             'access_token': '5005eb18-be6b-4ac0-b084-0443289b3378',
             'expires_in': 631138518,
             'orcid': '1234-5678-9101-112X',
         })
         self.admin_identity = auth.add_user_or_update_credentials({
             'name': 'Super Admin',
             'access_token': 'f7c64584-0750-4cb6-8c81-2932f5daabb8',
             'expires_in': 3600,
             'orcid': '1211-1098-7654-321X',
         }, (ActionNeed('accept-patch'),))
         db.commit()
Example #6
0
def registered():
    if not request.args['state'] == session.pop('state_token', None):
        abort(403)
    data = {
        'client_id': app.config['ORCID_CLIENT_ID'],
        'client_secret': app.config['ORCID_CLIENT_SECRET'],
        'code': request.args['code'],
        'grant_type': 'authorization_code',
        'redirect_uri': build_redirect_uri(cli=('cli' in request.args)),
        'scope': '/authenticate',
    }
    response = requests.post(
        'https://orcid.org/oauth/token',
        headers={'Accept': 'application/json'},
        allow_redirects=True, data=data)
    if not response.status_code == 200:
        app.logger.error('Response to request for ORCID credential was not OK')
        app.logger.error('Request: %s', data)
        app.logger.error('Response: %s', response.text)
    credentials = response.json()
    if 'name' not in credentials or len(credentials['name']) == 0:
        # User has made their name private, so just use their ORCID as name
        credentials['name'] = credentials['orcid']
    identity = auth.add_user_or_update_credentials(credentials)
    database.get_db().commit()
    if 'cli' in request.args:
        return make_response(
            ('Your token is: {}'.format(identity.b64token.decode()),
             {'Content-Type': 'text/plain'}))
    else:
        return make_response("""
        <!doctype html>
        <head>
            <script type="text/javascript">
            parent.postMessage(
              {{ name: {}, token: {} }},
              "{}"
            )
            window.close();
            </script>
        </head>
        <body>
        """.format(
            json.dumps(credentials['name']),
            json.dumps(identity.b64token.decode()),
            request.host_url
        ))
Example #7
0
def add_new_version_of_dataset(data):
    now = database.query_db(
        "SELECT CAST(strftime('%s', 'now') AS INTEGER) AS now", one=True)['now']
    cursor = database.get_db().cursor()
    cursor.execute(
        'INSERT into DATASET (data, description, created_at) VALUES (?,?,?)',
        (json.dumps(data), void.describe_dataset(data, now), now))
    return cursor.lastrowid
Example #8
0
def merge(patch_id, user_id):
    row = database.query_db(
        'SELECT * FROM patch_request WHERE id = ?', (patch_id,), one=True)

    if not row:
        raise MergeError('No patch with ID {}.'.format(patch_id))
    if row['merged']:
        raise MergeError('Patch is already merged.')
    if not row['open']:
        raise MergeError('Closed patches cannot be merged.')

    dataset = database.get_dataset()
    mergeable = is_mergeable(row['original_patch'], dataset)

    if not mergeable:
        raise UnmergeablePatchError('Patch is not mergeable.')

    data = json.loads(dataset['data'])
    original_patch = from_text(row['original_patch'])
    applied_patch, id_map = replace_skolem_ids(
        original_patch, data, database.get_removed_entity_keys())
    created_entities = set(id_map.values())

    # Should this be ordered?
    new_data = applied_patch.apply(data)

    db = database.get_db()
    curs = db.cursor()
    curs.execute(
        '''
        UPDATE patch_request
        SET merged = 1,
            open = 0,
            merged_at = strftime('%s', 'now'),
            merged_by = ?,
            applied_to = ?,
            created_entities = ?,
            identifier_map = ?,
            applied_patch = ?
        WHERE id = ?;
        ''',
        (user_id,
         dataset['id'],
         json.dumps(sorted(created_entities)),
         json.dumps(id_map),
         applied_patch.to_string(),
         row['id'])
    )
    version_id = add_new_version_of_dataset(new_data)
    curs.execute(
        '''
        UPDATE patch_request
        SET resulted_in = ?
        WHERE id = ?;
        ''',
        (version_id, row['id'])
    )
Example #9
0
def create_request(patch, user_id):
    dataset = database.get_dataset()
    affected_entities = validate(patch, dataset)
    cursor = database.get_db().cursor()
    cursor.execute('''
INSERT INTO patch_request
(created_by, updated_by, created_from, updated_entities, removed_entities,
 original_patch)
VALUES (?, ?, ?, ?, ?, ?)
    ''', (user_id, user_id, dataset['id'],
          json.dumps(sorted(affected_entities['updated'])),
          json.dumps(sorted(affected_entities['removed'])),
          patch.to_string()))
    return cursor.lastrowid
Example #10
0
def add_comment(patch_id, user_id, message):
    row = database.query_db(
        'SELECT * FROM patch_request WHERE id = ?', (patch_id,), one=True)

    if not row:
        raise MergeError('No patch with ID {}.'.format(patch_id))

    db = database.get_db()
    curs = db.cursor()
    curs.execute(
        '''
        INSERT INTO patch_request_comment
        (patch_request_id, author, message)
        VALUES (?, ?, ?)
        ''',
        (patch_id, user_id, message)
    )
Example #11
0
def reject(patch_id, user_id):
    row = database.query_db(
        'SELECT * FROM patch_request WHERE id = ?', (patch_id,), one=True)

    if not row:
        raise MergeError('No patch with ID {}.'.format(patch_id))
    if row['merged']:
        raise MergeError('Patch is already merged.')
    if not row['open']:
        raise MergeError('Closed patches cannot be merged.')

    db = database.get_db()
    curs = db.cursor()
    curs.execute(
        '''
        UPDATE patch_request
        SET merged = 0,
            open = 0,
            merged_at = strftime('%s', 'now'),
            merged_by = ?
        WHERE id = ?;
        ''',
        (user_id, row['id'],)
    )
Example #12
0
def add_user_or_update_credentials(credentials, extra_permissions=()):
    orcid = 'http://orcid.org/{}'.format(credentials['orcid'])
    b64token = b64encode(credentials['access_token'].encode())
    permissions = (ActionNeed('submit-patch'),) + extra_permissions
    db = database.get_db()
    cursor = db.cursor()
    cursor.execute('''
    INSERT OR IGNORE INTO user (
    id,
    name,
    permissions,
    b64token,
    token_expires_at,
    credentials)
    VALUES (?, ?, ?, ?, strftime('%s','now') + ?, ?) ''',
                   (orcid,
                    credentials['name'],
                    json.dumps(permissions),
                    b64token,
                    credentials['expires_in'],
                    json.dumps(credentials)))
    if not cursor.lastrowid:  # user with this id already in DB
        cursor.execute('''
        UPDATE user SET
        name = ?,
        b64token = ?,
        token_expires_at = strftime('%s','now') + ?,
        credentials = ?
        WHERE id = ?''',
                       (credentials['name'],
                        b64token,
                        credentials['expires_in'],
                        json.dumps(credentials),
                        orcid))

    return get_identity(b64token, cursor)
Example #13
0
def init_db():
    with app.app_context():
        db = database.get_db()
        with app.open_resource("schema.sql", mode="r") as schema_file:
            db.cursor().executescript(schema_file.read())
        db.commit()
Example #14
0
def make_nanopub(period_id, version):
    cursor = database.get_db().cursor()

    cursor.execute(
        '''
        SELECT
            patch.id as patch_id,

            patch.merged_at,
            patch.merged_by,
            patch.created_by,

            dataset.data
        FROM patch_request AS patch
        LEFT JOIN dataset ON patch.resulted_in = dataset.id
        WHERE
            patch.created_entities LIKE ?
            OR
            patch.updated_entities LIKE ?
        ORDER BY patch.id ASC
        LIMIT ?, 1;
        ''',
        ('%"' + identifier.prefix(period_id) + '"%',
         '%"' + identifier.prefix(period_id) + '"%',
         version - 1)
    )

    result = cursor.fetchone()

    if not result:
        raise PeriodNotFoundError(
            'Could not find version {} of period {}'.format(
                version, period_id))

    data = json.loads(result['data'])

    authority_id = identifier.prefix(
        period_id[:identifier.AUTHORITY_SEQUENCE_LENGTH + 1])
    authority = data['authorities'][authority_id]
    source = authority['source']
    period = authority['periods'][identifier.prefix(period_id)]
    period['authority'] = authority_id

    nanopub_uri = '{}/nanopub{}'.format(
        identifier.prefix(period_id), version)
    patch_uri = identifier.prefix('h#change-{}'.format(result['patch_id']))

    context = data['@context'].copy()
    context['np'] = 'http://nanopub.org/nschema#'
    context['pub'] = data['@context']['@base'] + nanopub_uri + '#'
    context['prov'] = 'http://www.w3.org/ns/prov#'

    # TODO: Pop "source" from period and include it in the provenance
    # graph?

    return {
        "@context": context,
        "@graph": [
            {
                "@id": "pub:head",
                "@graph": {
                    "@id": nanopub_uri,
                    "@type": "np:Nanopublication",
                    "np:hasAssertion": as_uri("pub:assertion"),
                    "np:hasProvenance": as_uri("pub:provenance"),
                    "np:hasPublicationInfo": as_uri("pub:pubinfo"),
                }
            },
            {
                "@id": "pub:assertion",
                "@graph": [period]
            },
            {
                "@id": "pub:provenance",
                "@graph": [
                    {
                        "@id": 'pub:assertion',
                        "dc:source": source
                    }
                ]
            },
            {
                "@id": "pub:pubinfo",
                "@graph": [
                    {
                        "@id": nanopub_uri,
                        "prov:wasGeneratedBy": as_uri(patch_uri),
                        "prov:asGeneratedAtTime": result['merged_at'],
                        "prov:wasAttributedTo": [
                            as_uri(result['merged_by']),
                            as_uri(result['created_by'])
                        ]
                    }
                ]
            }
        ]
    }
Example #15
0
def describe_dataset(data, created_at):
    cursor = database.get_db().cursor()
    contributors = cursor.execute('''
    SELECT DISTINCT created_by, updated_by
    FROM patch_request
    WHERE merged = 1
    AND id > 1''').fetchall()

    with open(os.path.join(os.path.dirname(__file__), 'void-stub.ttl')) as f:
        description_g = Graph().parse(file=f, format='turtle')
    ns = Namespace(description_g.value(
        predicate=RDF.type, object=VOID.DatasetDescription))
    dataset_g = Graph().parse(data=json.dumps(data), format='json-ld')

    partitions = description_g.objects(
        subject=ns.d, predicate=VOID.classPartition)
    for part in partitions:
        clazz = description_g.value(subject=part, predicate=VOID['class'])
        entity_count = len(dataset_g.query('''
        SELECT DISTINCT ?s
        WHERE {
          ?s a <%s> .
          FILTER (STRSTARTS(STR(?s), "%s"))
        }''' % (clazz, ns)))
        description_g.add(
            (part, VOID.entities, Literal(entity_count, datatype=XSD.integer)))

    linksets = description_g.subjects(predicate=RDF.type, object=VOID.Linkset)
    for linkset in linksets:
        target = description_g.value(
            subject=linkset, predicate=VOID.objectsTarget)
        predicate = description_g.value(
            subject=linkset, predicate=VOID.linkPredicate)
        uriSpace = description_g.value(
            subject=target, predicate=VOID.uriSpace).value
        triples = len(dataset_g.query('''
SELECT ?s ?p ?o
WHERE {
  ?s <%s> ?o .
  FILTER (STRSTARTS(STR(?o), "%s")) .
}''' % (predicate, uriSpace)))
        description_g.add(
            (linkset, VOID.triples, Literal(triples, datatype=XSD.integer)))

    def add_to_description(p, o):
        description_g.add((ns.d, p, o))

    add_to_description(
        DCTERMS.modified,
        Literal(utils.isoformat(created_at), datatype=XSD.dateTime))

    add_to_description(
        DCTERMS.provenance,
        URIRef(utils.absolute_url(data['@context']['@base'], 'history')
               + '#changes')
    )

    add_to_description(
        VOID.triples, Literal(len(dataset_g), datatype=XSD.integer))

    for row in contributors:
        add_to_description(
            DCTERMS.contributor, URIRef(row['created_by']))
        if row['updated_by']:
            add_to_description(
                DCTERMS.contributor, URIRef(row['updated_by']))

    return description_g.serialize(format='turtle')
Example #16
0
def history():
    g = Graph()
    changelog = Collection(g, URIRef("#changelog"))
    cursor = database.get_db().cursor()
    for row in cursor.execute(
        """
SELECT
  id,
  created_at,
  created_by,
  updated_by,
  merged_at,
  merged_by,
  applied_to,
  resulted_in,
  created_entities,
  updated_entities,
  removed_entities
FROM patch_request
WHERE merged = 1
ORDER BY id ASC
"""
    ).fetchall():
        change = URIRef("#change-{}".format(row["id"]))
        patch = URIRef("#patch-{}".format(row["id"]))
        g.add((patch, FOAF.page, PERIODO[identifier.prefix(url_for("patch", id=row["id"]))]))
        g.add((change, PROV.startedAtTime, Literal(utils.isoformat(row["created_at"]), datatype=XSD.dateTime)))
        g.add((change, PROV.endedAtTime, Literal(utils.isoformat(row["merged_at"]), datatype=XSD.dateTime)))
        dataset = PERIODO[identifier.prefix(url_for("abstract_dataset"))]
        version_in = PERIODO[identifier.prefix(url_for("abstract_dataset", version=row["applied_to"]))]
        g.add((version_in, PROV.specializationOf, dataset))
        version_out = PERIODO[identifier.prefix(url_for("abstract_dataset", version=row["resulted_in"]))]
        g.add((version_out, PROV.specializationOf, dataset))

        g.add((change, PROV.used, version_in))
        g.add((change, PROV.used, patch))
        g.add((change, PROV.generated, version_out))

        def add_entity_version(entity_id):
            entity = PERIODO[entity_id]
            entity_version = PERIODO[entity_id + "?version={}".format(row["resulted_in"])]
            g.add((entity_version, PROV.specializationOf, entity))
            g.add((change, PROV.generated, entity_version))
            return entity_version

        for entity_id in json.loads(row["created_entities"]):
            add_entity_version(entity_id)

        for entity_id in json.loads(row["updated_entities"]):
            entity_version = add_entity_version(entity_id)
            prev_entity_version = PERIODO[entity_id + "?version={}".format(row["applied_to"])]
            g.add((entity_version, PROV.wasRevisionOf, prev_entity_version))

        for entity_id in json.loads(row["removed_entities"]):
            g.add((change, PROV.invalidated, PERIODO[entity_id]))

        for field, term in (("created_by", "submitted"), ("updated_by", "updated"), ("merged_by", "merged")):
            if row[field] == "initial-data-loader":
                continue
            agent = URIRef(row[field])
            association = URIRef("#patch-{}-{}".format(row["id"], term))
            g.add((change, PROV.wasAssociatedWith, agent))
            g.add((change, PROV.qualifiedAssociation, association))
            g.add((association, PROV.agent, agent))
            g.add((association, PROV.hadRole, PERIODO[identifier.prefix(url_for("vocab") + "#" + term)]))

        changelog.append(change)

    def ordering(o):
        if o["@id"] == "#changelog":
            # sort first
            return " "
        return o["@id"]

    jsonld = json.loads(g.serialize(format="json-ld", context=CONTEXT).decode("utf-8"))
    jsonld["history"] = sorted(jsonld["history"], key=ordering)
    return json.dumps(jsonld, sort_keys=True)