From 35dee57a41e03a7a0d839dc5278c8864e6792421 Mon Sep 17 00:00:00 2001 From: Chlod Alejandro Date: Fri, 4 Oct 2024 13:00:16 +0800 Subject: [PATCH] Add OAuth flow Adds the initial backend requirements for the OAuth flow, which will be made required for all search engine checks. This adds three new routes: - `/login` (GET, POST) - for logging in - `/logout` (GET, POST) - for logging out - `/oauth-callback` - OAuth 1.0a callback route Login/logout state can be checked through the header. By default, a separate page navigation is not required when the link is clicked by the user. When following a link to `/log(in|out)`, however, an extra button will be shown to prevent inadvertent logins/logouts. --- .gitignore | 1 + README.md | 1 + app.py | 53 +++++++++++++++++++++++++++++++++++++++++-- copyvios/api.py | 3 +++ copyvios/auth.py | 48 +++++++++++++++++++++++++++++++++++++++ copyvios/misc.py | 3 ++- static/script.js | 39 +++++++++++++++++++++++++++++++ static/script.min.js | 2 +- static/style.css | 15 ++++++++++++ static/style.min.css | 2 +- templates/login.mako | 39 +++++++++++++++++++++++++++++++ templates/logout.mako | 22 ++++++++++++++++++ templates/support/header.mako | 8 ++++++- 13 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 copyvios/auth.py create mode 100644 templates/login.mako create mode 100644 templates/logout.mako diff --git a/.gitignore b/.gitignore index 17ff478..3b6b992 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ .earwigbot logs/* !logs/.gitinclude +config.py diff --git a/README.md b/README.md index 230bd66..a11ada3 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Dependencies * [flask-mako](https://pythonhosted.org/Flask-Mako/) >= 0.3 * [mako](https://www.makotemplates.org/) >= 0.7.2 * [mwparserfromhell](https://github.com/earwig/mwparserfromhell) >= 0.3 +* [mwoauth](https://github.com/mediawiki-utilities/python-mwoauth) == 0.3.8 * [oursql](https://pythonhosted.org/oursql/) >= 0.9.3.1 * [requests](https://requests.readthedocs.io/) >= 2.9.1 * [SQLAlchemy](https://www.sqlalchemy.org/) >= 0.9.6 diff --git a/app.py b/app.py index ff88f48..4e5f899 100755 --- a/app.py +++ b/app.py @@ -1,18 +1,19 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- - from functools import wraps from hashlib import md5 from json import dumps from logging import DEBUG, INFO, getLogger from logging.handlers import TimedRotatingFileHandler +from multiprocessing import Value from os import path from time import asctime from traceback import format_exc +from urllib import quote_plus, quote from earwigbot.bot import Bot from earwigbot.wiki.copyvios import globalize -from flask import Flask, g, make_response, request +from flask import Flask, g, make_response, request, redirect, session from flask_mako import MakoTemplates, render_template, TemplateError from copyvios.api import format_api_error, handle_api_request @@ -21,12 +22,14 @@ from copyvios.cookies import parse_cookies from copyvios.misc import cache, get_notice from copyvios.settings import process_settings from copyvios.sites import update_sites +from copyvios.auth import oauth_login_start, oauth_login_end, clear_login_session app = Flask(__name__) MakoTemplates(app) hand = TimedRotatingFileHandler("logs/app.log", when="midnight", backupCount=7) hand.setLevel(DEBUG) +app.config.from_pyfile("config.py", True) app.logger.addHandler(hand) app.logger.info(u"Flask server started " + asctime()) app._hash_cache = {} @@ -52,6 +55,12 @@ def setup_app(): cache.background_data = {} cache.last_background_updates = {} + oauth_config = cache.bot.config.wiki.get('copyvios', {}).get('oauth', {}) + if oauth_config.get('consumer_token') is None: + raise ValueError("No OAuth consumer token is configured (config.wiki.copyvios.oauth.consumer_token).") + if oauth_config.get('consumer_secret') is None: + raise ValueError("No OAuth consumer secret is configured (config.wiki.copyvios.oauth.consumer_secret).") + globalize(num_workers=8) @app.before_request @@ -101,10 +110,50 @@ def index(): notice = get_notice() update_sites() query = do_check() + if query.submitted and query.error == "not logged in": + return redirect("/login?next=" + quote("/?" + request.query_string), 302) + return render_template( "index.mako", notice=notice, query=query, result=query.result, turnitin_result=query.turnitin_result) +@app.route("/login", methods=["GET", "POST"]) +@catch_errors +def login(): + try: + redirect_url = oauth_login_start() if request.method == "POST" else None + if redirect_url: + return redirect(redirect_url, 302) + except Exception as e: + app.log_exception(e) + print e.message + kwargs = {"error": e.message} + else: + if session.get("username") is not None: + return redirect("/", 302) + kwargs = {"error": request.args.get("error")} + return render_template("login.mako", **kwargs) + +@app.route("/logout", methods=["GET", "POST"]) +@catch_errors +def logout(): + if request.method == "POST": + clear_login_session() + return redirect("/", 302) + else: + return render_template("logout.mako") + +@app.route("/oauth-callback") +@catch_errors +def oauth_callback(): + try: + next_url = oauth_login_end() + except Exception as e: + app.log_exception(e) + return redirect("/login?error=" + quote_plus(e.message), 302) + else: + return redirect(next_url, 302) + @app.route("/settings", methods=["GET", "POST"]) @catch_errors def settings(): diff --git a/copyvios/api.py b/copyvios/api.py index 703a0cb..f3443fe 100644 --- a/copyvios/api.py +++ b/copyvios/api.py @@ -10,6 +10,8 @@ from .sites import update_sites __all__ = ["format_api_error", "handle_api_request"] _CHECK_ERRORS = { + "not logged in": "You are required to log in with your Wikipedia account " + "to perform checks with the search engine", "no search method": "Either 'use_engine' or 'use_links' must be true", "bad oldid": "The revision ID is invalid", "no URL": "The parameter 'url' is required for URL comparisons", @@ -116,6 +118,7 @@ _HOOKS = { def handle_api_request(): query = Query() + query.api = True if query.version: try: query.version = int(query.version) diff --git a/copyvios/auth.py b/copyvios/auth.py new file mode 100644 index 0000000..c9793b0 --- /dev/null +++ b/copyvios/auth.py @@ -0,0 +1,48 @@ +import mwoauth +from flask import session, request +from .misc import cache + +__all__ = ["oauth_login_start", "oauth_login_end", "clear_login_session"] + +def oauth_login_start(): + consumer_token = mwoauth.ConsumerToken( + cache.bot.config.wiki["copyvios"]["oauth"]["consumer_token"], + cache.bot.config.wiki["copyvios"]["oauth"]["consumer_secret"]) + + redirect, request_token = mwoauth.initiate( + "https://meta.wikimedia.org/w/index.php", consumer_token) + session["request_token"] = dict(zip(request_token._fields, request_token)) + + # Take note of where to send the user after logging in + next_url = (request.form if request.method == "POST" else request.args).get("next", "/") + if next_url[0] == "/": + # Only allow internal redirects + session["next"] = next_url + + return redirect + +def oauth_login_end(): + if "request_token" not in session: + raise ValueError("OAuth request token not found in session.") + + consumer_token = mwoauth.ConsumerToken( + cache.bot.config.wiki["copyvios"]["oauth"]["consumer_token"], + cache.bot.config.wiki["copyvios"]["oauth"]["consumer_secret"]) + + access_token = mwoauth.complete( + "https://meta.wikimedia.org/w/index.php", + consumer_token, + mwoauth.RequestToken(**session["request_token"]), + request.query_string) + identity = mwoauth.identify( + "https://meta.wikimedia.org/w/index.php", + consumer_token, + access_token) + + session["access_token"] = dict(zip(access_token._fields, access_token)) + session["username"] = identity["username"] + + return session.get("next", "/") + +def clear_login_session(): + session.clear() \ No newline at end of file diff --git a/copyvios/misc.py b/copyvios/misc.py index b4cbca2..72e26d7 100644 --- a/copyvios/misc.py +++ b/copyvios/misc.py @@ -5,7 +5,7 @@ import datetime from os.path import expanduser, join import apsw -from flask import g, request +from flask import g, request, session import oursql from sqlalchemy.pool import manage @@ -19,6 +19,7 @@ class Query(object): data = request.form if method == "POST" else request.args for key in data: self.query[key] = data.getlist(key)[-1] + self.query["requester_username"] = session.get("username") def __getattr__(self, key): return self.query.get(key) diff --git a/static/script.js b/static/script.js index 3bdc77f..f5dc1f4 100644 --- a/static/script.js +++ b/static/script.js @@ -148,5 +148,44 @@ $(document).ready(function() { }); } + if ($(".login-link").length >= 0) { + $(".login-link").click(function(e) { + e.preventDefault(); + var $loginForm = $("
") + .attr("action", "/login") + .attr("method", "POST"); + + // Tell `/login` where to go after logging in + $loginForm.append( + $("") + .attr("type", "hidden") + .attr("name", "next") + .attr("value", window.location.pathname + window.location.search) + ) + + $("body").after($loginForm); + $loginForm.trigger("submit"); + $loginForm.remove(); + return false; + }); + } + + if ($(".logout-link").length >= 0) { + $(".logout-link").click(function(e) { + e.preventDefault(); + if (!confirm("Are you sure you want to log out?")) { + return; + } + + var $logoutForm = $("") + .attr("action", "/logout") + .attr("method", "POST"); + $("body").after($logoutForm); + $logoutForm.trigger("submit"); + $logoutForm.remove(); + return false; + }); + } + install_notice(); }); diff --git a/static/script.min.js b/static/script.min.js index 8ca38b7..a604770 100644 --- a/static/script.min.js +++ b/static/script.min.js @@ -1 +1 @@ -function update_screen_size(){var cache=cache_cookie(),data={width:window.screen.availWidth,height:window.screen.availHeight};cache&&cache.width==data.width&&cache.height==data.height||set_cookie("CopyviosScreenCache",JSON.stringify(data),1095)}function cache_cookie(){var cookie=get_cookie("CopyviosScreenCache");if(cookie)try{var width=(data=JSON.parse(cookie)).width,height=data.height;if(width&&height)return{width:width,height:height}}catch(SyntaxError){}return!1}function get_cookie(name){for(var nameEQ=name+"=",ca=document.cookie.split(";"),i=0;i",{id:"notice-collapse-trigger",href:"#",text:"[show]",click:function(){return toggle_notice(),!1}})),details.hide())}$(document).ready(function(){$("#action-search").change(function(){$(".cv-search").prop("disabled",!1),$(".cv-compare").prop("disabled",!0),$(".cv-search-oo-ui").addClass("oo-ui-widget-enabled").removeClass("oo-ui-widget-disabled"),$(".cv-compare-oo-ui").addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled")}),$("#action-compare").change(function(){$(".cv-search").prop("disabled",!0),$(".cv-compare").prop("disabled",!1),$(".cv-search-oo-ui").addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled"),$(".cv-compare-oo-ui").addClass("oo-ui-widget-enabled").removeClass("oo-ui-widget-disabled")}),$("#action-search").is(":checked")&&$("#action-search").change(),$("#action-compare").is(":checked")&&$("#action-compare").change(),$("#cv-form").submit(function(){$("#action-search").is(":checked")&&$.each([["engine","use_engine"],["links","use_links"],["turnitin","turnitin"]],function(i,val){$("#cv-cb-"+val[0]).is(":checked")&&$("#cv-form input[type='hidden'][name='"+val[1]+"']").prop("disabled",!0)}),$("#cv-form button[type='submit']").prop("disabled",!0).css("cursor","progress").parent().addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled")}),0<=$("#cv-additional").length&&($("#cv-additional").css("display","block"),$(".source-default-hidden").css("display","none"),$("#show-additional-sources").click(function(){return $(".source-default-hidden").css("display",""),$("#cv-additional").css("display","none"),!1})),install_notice()}); \ No newline at end of file +function update_screen_size(){var cache=cache_cookie(),data={width:window.screen.availWidth,height:window.screen.availHeight};cache&&cache.width==data.width&&cache.height==data.height||set_cookie("CopyviosScreenCache",JSON.stringify(data),1095)}function cache_cookie(){var cookie=get_cookie("CopyviosScreenCache");if(cookie)try{var width=(data=JSON.parse(cookie)).width,height=data.height;if(width&&height)return{width:width,height:height}}catch(SyntaxError){}return!1}function get_cookie(name){for(var nameEQ=name+"=",ca=document.cookie.split(";"),i=0;i",{id:"notice-collapse-trigger",href:"#",text:"[show]",click:function(){return toggle_notice(),!1}})),details.hide())}$(document).ready(function(){$("#action-search").change(function(){$(".cv-search").prop("disabled",!1),$(".cv-compare").prop("disabled",!0),$(".cv-search-oo-ui").addClass("oo-ui-widget-enabled").removeClass("oo-ui-widget-disabled"),$(".cv-compare-oo-ui").addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled")}),$("#action-compare").change(function(){$(".cv-search").prop("disabled",!0),$(".cv-compare").prop("disabled",!1),$(".cv-search-oo-ui").addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled"),$(".cv-compare-oo-ui").addClass("oo-ui-widget-enabled").removeClass("oo-ui-widget-disabled")}),$("#action-search").is(":checked")&&$("#action-search").change(),$("#action-compare").is(":checked")&&$("#action-compare").change(),$("#cv-form").submit(function(){$("#action-search").is(":checked")&&$.each([["engine","use_engine"],["links","use_links"],["turnitin","turnitin"]],function(i,val){$("#cv-cb-"+val[0]).is(":checked")&&$("#cv-form input[type='hidden'][name='"+val[1]+"']").prop("disabled",!0)}),$("#cv-form button[type='submit']").prop("disabled",!0).css("cursor","progress").parent().addClass("oo-ui-widget-disabled").removeClass("oo-ui-widget-enabled")}),0<=$("#cv-additional").length&&($("#cv-additional").css("display","block"),$(".source-default-hidden").css("display","none"),$("#show-additional-sources").click(function(){return $(".source-default-hidden").css("display",""),$("#cv-additional").css("display","none"),!1})),0<=$(".login-link").length&&$(".login-link").click(function(e){e.preventDefault();e=$("").attr("action","/login").attr("method","POST");return e.append($("").attr("type","hidden").attr("name","next").attr("value",window.location.pathname+window.location.search)),$("body").after(e),e.trigger("submit"),e.remove(),!1}),0<=$(".logout-link").length&&$(".logout-link").click(function(e){if(e.preventDefault(),confirm("Are you sure you want to log out?"))return e=$("").attr("action","/logout").attr("method","POST"),$("body").after(e),e.trigger("submit"),e.remove(),!1}),install_notice()}); \ No newline at end of file diff --git a/static/style.css b/static/style.css index 3c48d55..ec4134b 100644 --- a/static/style.css +++ b/static/style.css @@ -86,6 +86,21 @@ header h1 { } } +header .login-link, header .logout-link { + margin-right: 1em; +} + +header .login-link::before, header .logout-link::before { + content: ' '; + font-size: 0.85em; + color: black; + opacity: 0.6; + padding-left: 1.67em; + background-image: linear-gradient(transparent,transparent), url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%3E%3Ctitle%3Euser%20avatar%3C%2Ftitle%3E%3Cpath%20d%3D%22M10%2011c-5.92%200-8%203-8%205v3h16v-3c0-2-2.08-5-8-5%22%2F%3E%3Ccircle%20cx%3D%2210%22%20cy%3D%225.5%22%20r%3D%224.5%22%2F%3E%3C%2Fsvg%3E"); + background-repeat: no-repeat; + background-size: contain; +} + #settings-link::before { content: ' '; font-size: 0.85em; diff --git a/static/style.min.css b/static/style.min.css index e05b5c5..9b077a4 100644 --- a/static/style.min.css +++ b/static/style.min.css @@ -1 +1 @@ -body,html{height:100%;margin:0}body{background-attachment:fixed;background-color:#eaecf0;background-position:50%;background-size:cover;color:#000;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:14px}#container{flex:auto;line-height:1.25;margin:0 auto}#container.splash{display:flex;flex-direction:column;justify-content:center}@media only screen and (min-width:1200px){#container.splash{max-width:1600px;min-width:1200px}}@media only screen and (max-width:1200px){#container.splash{width:100%}}#container.splash>.padding{height:25%}#content{background-color:#fff;border:1px solid #c8ccd1;filter:drop-shadow(0 0 10px rgba(0,0,0,.25));margin:1.5em 3em;padding:1em}@media only screen and (max-width:1000px){#content{margin:1em}}@media only screen and (max-width:400px){#content{margin:0}}header{background-color:#eaecf0;padding:.2em 1em}header>*{display:inline-block;vertical-align:middle}header h1{font-size:2.25em;font-weight:400;margin:0 1em 0 0}@media only screen and (max-width:500px){header h1{font-size:1.5em}}#settings-link:before{background-image:linear-gradient(transparent,transparent),url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='20' height='20' viewBox='0 0 20 20'%3E%3Ctitle%3Esettings%3C/title%3E%3Cg transform='translate(10 10)'%3E%3Cpath id='a' d='M1.5-10h-3l-1 6.5h5m0 7h-5l1 6.5h3'/%3E%3Cuse xlink:href='%23a' transform='rotate(45)'/%3E%3Cuse xlink:href='%23a' transform='rotate(90)'/%3E%3Cuse xlink:href='%23a' transform='rotate(135)'/%3E%3C/g%3E%3Cpath d='M10 2.5a7.5 7.5 0 0 0 0 15 7.5 7.5 0 0 0 0-15v4a3.5 3.5 0 0 1 0 7 3.5 3.5 0 0 1 0-7'/%3E%3C/svg%3E");background-repeat:no-repeat;background-size:contain;color:#000;content:" ";font-size:.85em;opacity:.6;padding-left:1.67em}footer{background:#fff;border-top:1px solid #c8ccd1;font-size:.9em;line-height:1.5;padding:1em;text-align:center}footer ul{margin:0}footer li{display:inline}footer li:not(:last-child):after{content:" \00b7"}footer a{white-space:nowrap}ol,ul{line-height:1.5}h2{margin-bottom:.2em}#info-box,#notice-box{margin:1em 0;padding:0 1em}#notice-box ul{margin:0;padding-left:1.5em}#cv-result{margin:1em 0;padding:.5em}#attribution-warning{margin:1em 0;padding:1em}#sources-container,#turnitin-container{margin:1em 0;padding:.5em 1em 1em}#sources-container{background-color:#eee;border:1px solid #bbb}#sources-title,#turnitin-title{font-weight:700;margin-bottom:-.5em;text-align:center}#cv-additional{display:none}#generation-time{font-style:italic;text-align:right}@media only screen and (min-width:600px){#generation-time{margin-top:-1em}}#heading{width:100%}#cv-result-sources{border-spacing:0 .4em;table-layout:fixed;width:100%}#cv-result-sources col:first-child{width:80%}#cv-result-sources col:nth-child(2),#cv-result-sources col:nth-child(3){width:10%}#cv-result-sources th{text-align:left}#cv-result-sources tr:nth-child(2n){background-color:#e0e0e0}#cv-result-sources td:first-child{overflow:hidden;word-wrap:break-word}#cv-result-head-table{border-spacing:0;table-layout:fixed;text-align:center;width:100%}#cv-result-head-table col:nth-child(odd){width:42.5%}#cv-result-head-table col:nth-child(2){width:15%}#cv-result-head-table td:nth-child(odd){font-size:1.25em;font-weight:700;overflow:hidden;word-wrap:break-word}#cv-result-head-table td:nth-child(2) div:first-child{font-weight:700;white-space:nowrap}#cv-result-head-table td:nth-child(2) div:nth-child(2){font-size:2.5em;font-weight:700;line-height:1}#cv-result-head-table td:nth-child(2) div:nth-child(3){font-size:.8em}#cv-chain-table,#turnitin-table{border-spacing:0;table-layout:fixed;width:100%}#turnitin-table{word-wrap:break-word}#source-row-selected{background-color:#cfcfcf!important}#head-settings{text-align:right}#cv-result-header{margin:0}#redirected-from{font-size:.75em;font-weight:400}#redirected-from,#result-head-no-sources{font-style:italic}#source-selected{font-weight:700}#cv-cached{position:relative}#cv-cached span{background:#f3f3f3;border:1px solid #aaa;color:#000;display:none;font-style:normal;left:-5em;padding:1em;position:absolute;text-align:left;top:1.5em;width:30em;z-index:1}.green-box{background-color:#efe;border:1px solid #7f7}.yellow-box{background-color:#ffd;border:1px solid #ee5}.red-box{background-color:#fee;border:1px solid #f77}.gray-box{background-color:#eee;border:1px solid #aaa}.indentable{white-space:pre-wrap}.cv-source-footer{font-style:italic;padding-bottom:.5em}.cv-source-footer a{font-style:normal}.cv-chain-detail{background-color:#fff;border:1px solid #bbb;padding:0 1em}.cv-chain-cell{vertical-align:top;word-wrap:break-word}.cv-chain-cell:first-child{padding-right:.5em}.cv-chain-cell:last-child{padding-left:.5em}.turnitin-table-cell{padding:.5em 0 .3em}.turnitin-table-cell ul{line-height:1.4;margin:.2em 0 0}.cv-hl{background:#faa}.cv-hl-in{background:#fcc;background:linear-gradient(270deg,#faa,#fff)}.cv-hl-out{background:#fcc;background:linear-gradient(90deg,#faa,#fff)}.mono{font-family:monospace}.light{color:#ccc}.medium{color:#aaa}.source-similarity{font-weight:700}.source-suspect{color:#900}.source-possible{color:#990}.source-novio{color:#090}.source-excluded,.source-skipped{font-style:italic}a:link,a:visited{color:#002bb8;text-decoration:none}a:hover{color:#002bb8}a:active,a:hover{text-decoration:underline}a:active{color:#404}header a:hover,header a:link,header a:visited{color:#54595d}header a:active{color:#333}#cv-cached:active{color:#040}#cv-cached:active,#cv-cached:hover{text-decoration:none}#cv-cached:hover span{display:block}.source-url:link,.source-url:visited{color:#357}.source-url:hover{color:#035}.source-url:active{color:#404}.oo-ui-horizontalLayout>.oo-ui-dropdownInputWidget,.oo-ui-horizontalLayout>.oo-ui-textInputWidget{width:auto}.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline{hyphens:manual}.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-header,.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-header{width:10%}.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-field,.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-field{width:90%}.compare-url.oo-ui-textInputWidget,.page-title.oo-ui-textInputWidget{width:60%}.page-oldid.oo-ui-textInputWidget{width:10em}label.page,label.site{min-width:4em}label.action{min-width:10em}@media only screen and (max-width:720px){.oo-ui-horizontalLayout>.oo-ui-widget{width:100%}} \ No newline at end of file +body,html{height:100%;margin:0}body{background-attachment:fixed;background-color:#eaecf0;background-position:50%;background-size:cover;color:#000;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:14px}#container{flex:auto;line-height:1.25;margin:0 auto}#container.splash{display:flex;flex-direction:column;justify-content:center}@media only screen and (min-width:1200px){#container.splash{max-width:1600px;min-width:1200px}}@media only screen and (max-width:1200px){#container.splash{width:100%}}#container.splash>.padding{height:25%}#content{background-color:#fff;border:1px solid #c8ccd1;filter:drop-shadow(0 0 10px rgba(0,0,0,.25));margin:1.5em 3em;padding:1em}@media only screen and (max-width:1000px){#content{margin:1em}}@media only screen and (max-width:400px){#content{margin:0}}header{background-color:#eaecf0;padding:.2em 1em}header>*{display:inline-block;vertical-align:middle}header h1{font-size:2.25em;font-weight:400;margin:0 1em 0 0}@media only screen and (max-width:500px){header h1{font-size:1.5em}}header .login-link,header .logout-link{margin-right:1em}header .login-link:before,header .logout-link:before{background-image:linear-gradient(transparent,transparent),url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Ctitle%3Euser avatar%3C/title%3E%3Cpath d='M10 11c-5.92 0-8 3-8 5v3h16v-3c0-2-2.08-5-8-5'/%3E%3Ccircle cx='10' cy='5.5' r='4.5'/%3E%3C/svg%3E")}#settings-link:before,header .login-link:before,header .logout-link:before{background-repeat:no-repeat;background-size:contain;color:#000;content:" ";font-size:.85em;opacity:.6;padding-left:1.67em}#settings-link:before{background-image:linear-gradient(transparent,transparent),url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='20' height='20' viewBox='0 0 20 20'%3E%3Ctitle%3Esettings%3C/title%3E%3Cg transform='translate(10 10)'%3E%3Cpath id='a' d='M1.5-10h-3l-1 6.5h5m0 7h-5l1 6.5h3'/%3E%3Cuse xlink:href='%23a' transform='rotate(45)'/%3E%3Cuse xlink:href='%23a' transform='rotate(90)'/%3E%3Cuse xlink:href='%23a' transform='rotate(135)'/%3E%3C/g%3E%3Cpath d='M10 2.5a7.5 7.5 0 0 0 0 15 7.5 7.5 0 0 0 0-15v4a3.5 3.5 0 0 1 0 7 3.5 3.5 0 0 1 0-7'/%3E%3C/svg%3E")}footer{background:#fff;border-top:1px solid #c8ccd1;font-size:.9em;line-height:1.5;padding:1em;text-align:center}footer ul{margin:0}footer li{display:inline}footer li:not(:last-child):after{content:" \00b7"}footer a{white-space:nowrap}ol,ul{line-height:1.5}h2{margin-bottom:.2em}#info-box,#notice-box{margin:1em 0;padding:0 1em}#notice-box ul{margin:0;padding-left:1.5em}#cv-result{margin:1em 0;padding:.5em}#attribution-warning{margin:1em 0;padding:1em}#sources-container,#turnitin-container{margin:1em 0;padding:.5em 1em 1em}#sources-container{background-color:#eee;border:1px solid #bbb}#sources-title,#turnitin-title{font-weight:700;margin-bottom:-.5em;text-align:center}#cv-additional{display:none}#generation-time{font-style:italic;text-align:right}@media only screen and (min-width:600px){#generation-time{margin-top:-1em}}#heading{width:100%}#cv-result-sources{border-spacing:0 .4em;table-layout:fixed;width:100%}#cv-result-sources col:first-child{width:80%}#cv-result-sources col:nth-child(2),#cv-result-sources col:nth-child(3){width:10%}#cv-result-sources th{text-align:left}#cv-result-sources tr:nth-child(2n){background-color:#e0e0e0}#cv-result-sources td:first-child{overflow:hidden;word-wrap:break-word}#cv-result-head-table{border-spacing:0;table-layout:fixed;text-align:center;width:100%}#cv-result-head-table col:nth-child(odd){width:42.5%}#cv-result-head-table col:nth-child(2){width:15%}#cv-result-head-table td:nth-child(odd){font-size:1.25em;font-weight:700;overflow:hidden;word-wrap:break-word}#cv-result-head-table td:nth-child(2) div:first-child{font-weight:700;white-space:nowrap}#cv-result-head-table td:nth-child(2) div:nth-child(2){font-size:2.5em;font-weight:700;line-height:1}#cv-result-head-table td:nth-child(2) div:nth-child(3){font-size:.8em}#cv-chain-table,#turnitin-table{border-spacing:0;table-layout:fixed;width:100%}#turnitin-table{word-wrap:break-word}#source-row-selected{background-color:#cfcfcf!important}#head-settings{text-align:right}#cv-result-header{margin:0}#redirected-from{font-size:.75em;font-weight:400}#redirected-from,#result-head-no-sources{font-style:italic}#source-selected{font-weight:700}#cv-cached{position:relative}#cv-cached span{background:#f3f3f3;border:1px solid #aaa;color:#000;display:none;font-style:normal;left:-5em;padding:1em;position:absolute;text-align:left;top:1.5em;width:30em;z-index:1}.green-box{background-color:#efe;border:1px solid #7f7}.yellow-box{background-color:#ffd;border:1px solid #ee5}.red-box{background-color:#fee;border:1px solid #f77}.gray-box{background-color:#eee;border:1px solid #aaa}.indentable{white-space:pre-wrap}.cv-source-footer{font-style:italic;padding-bottom:.5em}.cv-source-footer a{font-style:normal}.cv-chain-detail{background-color:#fff;border:1px solid #bbb;padding:0 1em}.cv-chain-cell{vertical-align:top;word-wrap:break-word}.cv-chain-cell:first-child{padding-right:.5em}.cv-chain-cell:last-child{padding-left:.5em}.turnitin-table-cell{padding:.5em 0 .3em}.turnitin-table-cell ul{line-height:1.4;margin:.2em 0 0}.cv-hl{background:#faa}.cv-hl-in{background:#fcc;background:linear-gradient(270deg,#faa,#fff)}.cv-hl-out{background:#fcc;background:linear-gradient(90deg,#faa,#fff)}.mono{font-family:monospace}.light{color:#ccc}.medium{color:#aaa}.source-similarity{font-weight:700}.source-suspect{color:#900}.source-possible{color:#990}.source-novio{color:#090}.source-excluded,.source-skipped{font-style:italic}a:link,a:visited{color:#002bb8;text-decoration:none}a:hover{color:#002bb8}a:active,a:hover{text-decoration:underline}a:active{color:#404}header a:hover,header a:link,header a:visited{color:#54595d}header a:active{color:#333}#cv-cached:active{color:#040}#cv-cached:active,#cv-cached:hover{text-decoration:none}#cv-cached:hover span{display:block}.source-url:link,.source-url:visited{color:#357}.source-url:hover{color:#035}.source-url:active{color:#404}.oo-ui-horizontalLayout>.oo-ui-dropdownInputWidget,.oo-ui-horizontalLayout>.oo-ui-textInputWidget{width:auto}.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline{hyphens:manual}.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-header,.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-header{width:10%}.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-field,.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right>.oo-ui-fieldLayout-body>.oo-ui-fieldLayout-field{width:90%}.compare-url.oo-ui-textInputWidget,.page-title.oo-ui-textInputWidget{width:60%}.page-oldid.oo-ui-textInputWidget{width:10em}label.page,label.site{min-width:4em}label.action{min-width:10em}@media only screen and (max-width:720px){.oo-ui-horizontalLayout>.oo-ui-widget{width:100%}} \ No newline at end of file diff --git a/templates/login.mako b/templates/login.mako new file mode 100644 index 0000000..4ab6137 --- /dev/null +++ b/templates/login.mako @@ -0,0 +1,39 @@ +<%! + from json import dumps, loads + from flask import g, request + from copyvios.misc import cache + from urlparse import parse_qsl +%>\ +<%include file="/support/header.mako" args="title='Login | Earwig\'s Copyvio Detector', splash=True"/> +% if error: +
+

Error trying to log in: ${error | h}

+
+% endif +

Login

+

You are required to log in with your Wikimedia account to perform checks with the search engine.

+%if request.args.get('next'): +

+ After logging in, + % if request.args["next"][0:2] == "/?" and dict(parse_qsl(request.args["next"])).get("action") == "search": + your check will be run. + % else: + you will be redirected to: ${request.args.get('next') | h} + % endif +

+%endif + + +
+
+ + + + + +
+
+ +<%include file="/support/footer.mako"/> diff --git a/templates/logout.mako b/templates/logout.mako new file mode 100644 index 0000000..3d28a45 --- /dev/null +++ b/templates/logout.mako @@ -0,0 +1,22 @@ +<%! + from json import dumps, loads + from flask import g, request + from copyvios.misc import cache +%>\ +<%include file="/support/header.mako" args="title='Logout | Earwig\'s Copyvio Detector', splash=True"/> +

Logout

+

Logging out will prevent you from making search engine checks.

+
+
+
+ + + + + +
+
+
+<%include file="/support/footer.mako"/> diff --git a/templates/support/header.mako b/templates/support/header.mako index d6e7a76..19675c5 100644 --- a/templates/support/header.mako +++ b/templates/support/header.mako @@ -1,6 +1,6 @@ <%page args="title, splash=False"/>\ <%! - from flask import g, request, url_for + from flask import g, request, session, url_for from copyvios.background import set_background %>\ @@ -25,6 +25,12 @@

Earwig's Copyvio Detector

+ <% logged_in_user = session.get("username") %>\ + % if logged_in_user: + ${logged_in_user | h} + % else: + + % endif Settings