summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/fatcat_web/editing_routes.py16
-rw-r--r--python/fatcat_web/hacks.py27
-rw-r--r--python/fatcat_web/routes.py30
-rw-r--r--python/fatcat_web/templates/entity_macros.html14
-rw-r--r--python/fatcat_web/templates/release_view.html4
-rw-r--r--python/fatcat_web/templates/webcapture_view.html18
-rw-r--r--python/pytest.ini1
-rw-r--r--python/tests/web_auth.py6
-rw-r--r--rust/migrations/2019-01-01-000000_init/up.sql4
-rw-r--r--rust/src/auth.rs29
-rw-r--r--rust/tests/test_auth.rs7
11 files changed, 122 insertions, 34 deletions
diff --git a/python/fatcat_web/editing_routes.py b/python/fatcat_web/editing_routes.py
index 29db3443..98e5c27d 100644
--- a/python/fatcat_web/editing_routes.py
+++ b/python/fatcat_web/editing_routes.py
@@ -30,7 +30,9 @@ def form_editgroup_get_or_create(api, edit_form):
return None
app.log.warning(ae)
abort(ae.status)
- # TODO: check here that editgroup hasn't been merged already
+ if eg.changelog_index:
+ edit_form.editgroup_id.errors.append("Editgroup has already been accepted")
+ return None
else:
# if no editgroup, create one from description
try:
@@ -67,6 +69,8 @@ def container_create():
abort(ae.status)
# redirect to new entity
return redirect('/container/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
@@ -102,6 +106,8 @@ def container_edit(ident):
# redirect to entity revision
# TODO: container_rev_view
return redirect('/container/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
@@ -144,6 +150,8 @@ def file_create():
abort(ae.status)
# redirect to new entity
return redirect('/file/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
@@ -181,6 +189,8 @@ def file_edit(ident):
# redirect to entity revision
# TODO: file_rev_view
return redirect('/file/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
@@ -234,6 +244,8 @@ def release_create():
abort(ae.status)
# redirect to new release
return redirect('/release/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
@@ -270,6 +282,8 @@ def release_edit(ident):
# redirect to entity revision
# TODO: release_rev_view
return redirect('/release/{}'.format(edit.ident))
+ else:
+ status = 400
elif form.errors:
status = 400
app.log.info("form errors (did not validate): {}".format(form.errors))
diff --git a/python/fatcat_web/hacks.py b/python/fatcat_web/hacks.py
new file mode 100644
index 00000000..9e6f6ab5
--- /dev/null
+++ b/python/fatcat_web/hacks.py
@@ -0,0 +1,27 @@
+
+import re
+
+STRIP_EXTLINK_XML_RE = re.compile(r"<ext-link.*xlink:type=\"simple\">")
+
+def strip_extlink_xml(unstr):
+ unstr = unstr.replace("</ext-link>", "")
+ unstr = STRIP_EXTLINK_XML_RE.sub("", unstr)
+ return unstr
+
+def test_strip_extlink_xml():
+ assert strip_extlink_xml("asdf") == "asdf"
+ assert strip_extlink_xml("""LOCKSS (2014) Available: <ext-link xmlns:xlink="http://www.w3.org/1999/xlink" ext-link-type="uri" xlink:href="http://lockss.org/" xlink:type="simple">http://lockss.org/</ext-link>. Accessed: 2014 November 1.""") == \
+ """LOCKSS (2014) Available: http://lockss.org/. Accessed: 2014 November 1."""
+
+def wayback_suffix(entity):
+ """
+ Takes a webcapture entity and returns a suffix to be appended to wayback URLs
+ """
+ ret = ""
+ if entity.original_url:
+ if entity.timestamp:
+ ret = entity.timestamp.strftime("%Y%m%d%H%M%S/")
+ else:
+ ret = "*/"
+ ret += entity.original_url
+ return ret
diff --git a/python/fatcat_web/routes.py b/python/fatcat_web/routes.py
index 3479a937..f7f10f44 100644
--- a/python/fatcat_web/routes.py
+++ b/python/fatcat_web/routes.py
@@ -13,6 +13,7 @@ from fatcat_web import app, api, auth_api, priv_api
from fatcat_web.auth import handle_token_login, handle_logout, load_user, handle_ia_xauth
from fatcat_web.cors import crossdomain
from fatcat_web.search import *
+from fatcat_web.hacks import strip_extlink_xml, wayback_suffix
### Views ###################################################################
@@ -231,6 +232,8 @@ def webcapture_view(ident):
entity.releases.append(api.get_release(r))
except ApiException as ae:
abort(ae.status)
+ entity.wayback_suffix = wayback_suffix(entity)
+ print("SUFFIX: {}".format(entity.wayback_suffix))
return render_template('webcapture_view.html', webcapture=entity)
@app.route('/release/lookup', methods=['GET'])
@@ -266,8 +269,6 @@ def release_history(ident):
def release_view(ident):
try:
entity = api.get_release(ident, expand="container,files,filesets,webcaptures")
- container = entity.container
- filesets = entity.filesets
except ApiException as ae:
abort(ae.status)
if entity.state == "redirect":
@@ -278,13 +279,23 @@ def release_view(ident):
entity.container.es = container_to_elasticsearch(entity.container, force_bool=False)
if entity.state == "active":
entity.es = release_to_elasticsearch(entity, force_bool=False)
- for fs in filesets:
+ for fs in entity.filesets:
fs.total_size = sum([f.size for f in fs.manifest])
- entity.filesets = filesets
+ for wc in entity.webcaptures:
+ wc.wayback_suffix = wayback_suffix(wc)
+ for ref in entity.refs:
+ # this is a UI hack to get rid of XML crud in unstructured refs like:
+ # LOCKSS (2014) Available: <ext-link
+ # xmlns:xlink="http://www.w3.org/1999/xlink" ext-link-type="uri"
+ # xlink:href="http://lockss.org/"
+ # xlink:type="simple">http://lockss.org/</ext-link>. Accessed: 2014
+ # November 1.
+ if ref.extra and ref.extra.get('unstructured'):
+ ref.extra['unstructured'] = strip_extlink_xml(ref.extra['unstructured'])
authors = [c for c in entity.contribs if c.role in ('author', None)]
authors = sorted(authors, key=lambda c: c.index or 99999999)
return render_template('release_view.html', release=entity,
- authors=authors, container=container)
+ authors=authors, container=entity.container)
@app.route('/work/<ident>/history', methods=['GET'])
def work_history(ident):
@@ -382,6 +393,9 @@ def editgroup_accept(ident):
except ApiException as ae:
app.log.info(ae)
abort(ae.status)
+ # clear active_editgroup_id cookie; this doesn't cover all cases
+ if eg.editgroup_id == session.get('active_editgroup_id'):
+ session.pop('active_editgroup_id')
return redirect('/editgroup/{}'.format(ident))
@app.route('/editgroup/<ident>/unsubmit', methods=['POST'])
@@ -650,7 +664,11 @@ def logout():
@app.route('/auth/account')
@login_required
def auth_account():
- editor = api.get_editor(session['editor']['editor_id'])
+ # auth check on account page
+ user_api = auth_api(session['api_token'])
+ resp = user_api.auth_check()
+ assert(resp.success)
+ editor = user_api.get_editor(session['editor']['editor_id'])
session['editor'] = editor.to_dict()
load_user(editor.editor_id)
return render_template('auth_account.html')
diff --git a/python/fatcat_web/templates/entity_macros.html b/python/fatcat_web/templates/entity_macros.html
index 90c8e952..cefb0378 100644
--- a/python/fatcat_web/templates/entity_macros.html
+++ b/python/fatcat_web/templates/entity_macros.html
@@ -73,16 +73,22 @@
{%- endmacro %}
-{% macro url_list(urls) -%}
+{% macro url_list(urls, wayback_suffix="") -%}
<table class="ui very basic compact single line fixed table">
<tbody>
{% for url in urls %}
+ {% if url.rel == "wayback" %}
+ {% set suffix = wayback_suffix %}
+ {% else %}
+ {% set suffix = "" %}
+ {% endif %}
+ {% set entity = release %}
<tr><td class="two wide right aligned">{{ url.rel }}
- <td class="eight wide"><small><code><a href="{{ url.url }}">
+ <td class="eight wide"><small><code><a href="{{ url.url }}{{ suffix }}">
{% if url.url.count('/') >= 3 and url.rel != "dweb" %}
- {{ '/'.join(url.url.split('/')[0:2]) }}/<b>{{ ''.join(url.url.split('/')[2]) }}</b>/{{ '/'.join(url.url.split('/')[3:]) }}
+ {{ '/'.join(url.url.split('/')[0:2]) }}/<b>{{ ''.join(url.url.split('/')[2]) }}</b>/{{ '/'.join(url.url.split('/')[3:]) }}{{ suffix }}
{% else %}
- {{ url.url }}
+ {{ url.url }}{{ suffix }}
{% endif %}
</a></code></small>
{% endfor %}
diff --git a/python/fatcat_web/templates/release_view.html b/python/fatcat_web/templates/release_view.html
index 4849842c..403af653 100644
--- a/python/fatcat_web/templates/release_view.html
+++ b/python/fatcat_web/templates/release_view.html
@@ -267,7 +267,7 @@
<br><small><code><a href="/webcapture/{{ webcapture.ident }}">webcapture:{{ webcapture.ident }}</a></code></small>
<td class="single line">
{% for url in webcapture.archive_urls[:5] %}
- <a href="{{ url.url }}{% if url.rel == "wayback" %}*/{{ webcapture.original_url }}{% endif %}">{{ url.url.split('/')[2] }}</a> ({{ url.rel }})<br>
+ <a href="{{ url.url }}{% if url.rel == "wayback" %}{{ webcapture.wayback_suffix }}{% endif %}">{{ url.url.split('/')[2] }}</a> ({{ url.rel }})<br>
{% endfor %}
{% if webcapture.urls|length > 5 %}
+ {{ file.urls|length - 5 }} more URLs
@@ -318,7 +318,7 @@
{% if entity.files != [] and entity.files[0].urls != [] %}
<a href="{{ entity.files[0].urls[0].url }}" class="ui top attached fluid huge green button"><i class="file pdf outline icon"></i>Download Full Text</a>
{% elif entity.webcaptures != [] and entity.webcaptures[0].archive_urls != [] and entity.webcaptures[0].archive_urls[0].rel == "wayback" %}
-<a href="{{ entity.webcaptures[0].archive_urls[0].url }}*/{{ entity.webcaptures[0].original_url }}" class="ui top attached fluid huge green button"><i class="file archive outline icon"></i>View Web Archive</a>
+<a href="{{ entity.webcaptures[0].archive_urls[0].url }}{{ entity.webcaptures[0].wayback_suffix }}" class="ui top attached fluid huge green button"><i class="file archive outline icon"></i>View Web Archive</a>
{% else %}
<span class="ui top attached fluid huge grey button"><i class="file cross icon"></i>No Full Text Available</span>
{% endif %}
diff --git a/python/fatcat_web/templates/webcapture_view.html b/python/fatcat_web/templates/webcapture_view.html
index 1dd179f6..919f31e1 100644
--- a/python/fatcat_web/templates/webcapture_view.html
+++ b/python/fatcat_web/templates/webcapture_view.html
@@ -33,6 +33,14 @@
This Web Capture is not associated with any fatcat release.
{% endif %}
+<br>
+<h3>Archive URLs</h3>
+{% if webcapture.archive_urls != None %}
+ {{ entity_macros.url_list(webcapture.archive_urls, webcapture.wayback_suffix) }}
+{% else %}
+No known public archive for this webcapture.
+{% endif %}
+
<h3>CDX Rows ({{ webcapture.cdx|count }})</h3>
{% if webcapture.cdx %}
@@ -57,15 +65,7 @@
{% endfor %}
</div>
{% else %}
-This File Set is empty (contains no files).
-{% endif %}
-
-<br>
-<h3>Archive URLs</h3>
-{% if webcapture.archive_urls != None %}
- {{ entity_macros.url_list(webcapture.archive_urls) }}
-{% else %}
-No known public archive for this webcapture.
+This web capture is empty (contains no resources).
{% endif %}
</div>
diff --git a/python/pytest.ini b/python/pytest.ini
index 50a6f867..444333ea 100644
--- a/python/pytest.ini
+++ b/python/pytest.ini
@@ -13,5 +13,6 @@ addopts = --pylint --pylint-rcfile=.pylintrc --pylint-error-types=EF --pylint-jo
filterwarnings =
ignore:.*common_exception_handling.*StopIteration:PendingDeprecationWarning
ignore:passing extensions and flags as constants is deprecated:DeprecationWarning
+ ignore:.*authlib.specs.*:authlib.deprecate.AuthlibDeprecationWarning
log_level = INFO
diff --git a/python/tests/web_auth.py b/python/tests/web_auth.py
index 2fa9363b..b5839c6f 100644
--- a/python/tests/web_auth.py
+++ b/python/tests/web_auth.py
@@ -21,6 +21,9 @@ def test_ia_xauth_fail(full_app):
data=dict(email="abcd@example.com", password="god"))
assert rv.status_code == 401
+ rv = app.get('/auth/account', follow_redirects=False)
+ assert rv.status_code == 302
+
@responses.activate
def test_ia_xauth(full_app):
@@ -41,6 +44,9 @@ def test_ia_xauth(full_app):
data=dict(email="abcd@example.com", password="god"))
assert rv.status_code == 200
+ rv = app.get('/auth/account', follow_redirects=False)
+ assert rv.status_code == 200
+
def test_basic_auth_views(app):
rv = app.get('/auth/login')
diff --git a/rust/migrations/2019-01-01-000000_init/up.sql b/rust/migrations/2019-01-01-000000_init/up.sql
index b97660eb..5211b29a 100644
--- a/rust/migrations/2019-01-01-000000_init/up.sql
+++ b/rust/migrations/2019-01-01-000000_init/up.sql
@@ -619,8 +619,8 @@ INSERT INTO webcapture_rev_cdx (webcapture_rev, surt, timestamp, url, mimetype,
('00000000-0000-0000-7777-FFF000000003', 'org,asheesh)/robots.txt', '2003-02-17T04:47:19Z', 'http://asheesh.org:80/robots.txt', 'text/html', 404, 'a637f1d27d9bcb237310ed29f19c07e1c8cf0aa5', 'ffc1005680cb620eec4c913437dfabbf311b535cfe16cbaeb2faec1f92afc362');
INSERT INTO webcapture_rev_url (webcapture_rev, rel, url) VALUES
- ('00000000-0000-0000-7777-FFF000000002', 'wayback', 'http://web.archive.org/web/201801010001/http://example.org'),
- ('00000000-0000-0000-7777-FFF000000003', 'wayback', 'http://web.archive.org/web/201801010001/https://asheesh.org'),
+ ('00000000-0000-0000-7777-FFF000000002', 'wayback', 'http://web.archive.org/web/'),
+ ('00000000-0000-0000-7777-FFF000000003', 'wayback', 'http://web.archive.org/web/'),
('00000000-0000-0000-7777-FFF000000003', 'warc', 'https://example.org/something.warc.gz');
INSERT INTO webcapture_ident (id, is_live, rev_id, redirect_id) VALUES
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index 7e9b945a..a62c2f58 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -229,7 +229,7 @@ impl AuthConfectionary {
if let Some(duration) = duration {
let expires = now_utc + duration;
mac.add_first_party_caveat(&format!(
- "time < {:?}",
+ "time < {}",
&expires.to_rfc3339_opts(SecondsFormat::Secs, true)
));
};
@@ -291,12 +291,15 @@ impl AuthConfectionary {
let mut created: Option<DateTime<Utc>> = None;
for caveat in mac.first_party_caveats() {
if caveat.predicate().starts_with("time > ") {
- created = Some(
+ let ts: chrono::ParseResult<DateTime<Utc>> =
DateTime::parse_from_rfc3339(caveat.predicate().get(7..).unwrap())
- .unwrap()
- .with_timezone(&Utc),
- );
- break;
+ .map(|x| x.with_timezone(&Utc));
+ if let Ok(ts) = ts {
+ created = Some(ts);
+ break;
+ } else {
+ info!("couldn't parse macaroon time constraint: {}", caveat.predicate());
+ }
}
}
let created = match created {
@@ -337,10 +340,16 @@ impl AuthConfectionary {
verifier.satisfy_general(|p: &str| -> bool {
// not expired (based on time)
if p.starts_with("time < ") {
- let expires: DateTime<Utc> = DateTime::parse_from_rfc3339(p.get(7..).unwrap())
- .unwrap()
- .with_timezone(&Utc);
- expires < Utc::now()
+ let expires: chrono::ParseResult<DateTime<Utc>> =
+ DateTime::parse_from_rfc3339(p.get(7..).unwrap())
+ .map(|x| x.with_timezone(&Utc));
+ if let Ok(when) = expires {
+ //info!("checking time constraint: {} < {}", Utc::now(), when);
+ Utc::now() < when
+ } else {
+ info!("couldn't parse macaroon time constraint: {}", p);
+ false
+ }
} else {
false
}
diff --git a/rust/tests/test_auth.rs b/rust/tests/test_auth.rs
index c0d81753..2faf78ec 100644
--- a/rust/tests/test_auth.rs
+++ b/rust/tests/test_auth.rs
@@ -34,6 +34,13 @@ fn test_auth_db() {
let editor_row = c.parse_macaroon_token(&conn, &token, None).unwrap();
assert_eq!(editor_row.id, editor_id.to_uuid());
+ // create token w/ expiration
+ let token = c.create_token(editor_id, Some(chrono::Duration::days(1))).unwrap();
+
+ // verify token w/ expiration
+ let editor_row = c.parse_macaroon_token(&conn, &token, None).unwrap();
+ assert_eq!(editor_row.id, editor_id.to_uuid());
+
// revoke token
auth::revoke_tokens(&conn, editor_id).unwrap();