* Add Markdown as a known file-type.
+2012-09-17 Justin J. Novack <jnovack@gmail.com>
+
+ * Add OpenID authentication
+
2012-09-16 Christian Weiske <cweiske@cweiske.de>
* Implement request #12: DOAP documents for all pastes
List all pastes, with optional page
``/new``
Shows form for new paste
+``/login``
+ Login page for protecting site
+``/user``
+ Edit logged-in user information
+
Internal directory layout
=========================
rewrite ^/search$ /search.php;
rewrite ^/search/([0-9]+)$ /search.php?page=$1;
+
+ rewrite ^/login$ /login.php;
+ rewrite ^/user$ /user.php;
}
'geshi' => 'MediaWiki/geshi/geshi/geshi.php',
'index' => 'new'//"new" or "list"
);
+$GLOBALS['phorkie']['auth'] = array(
+ // 0 = public, no authentication, 1 = protect adds/edits/deletes,
+ // 2 = require authentication
+ 'securityLevel' => 0,
+ 'listedUsersOnly' => false,
+ 'users' => array(), // Array of OpenIDs that may login
+ 'anonymousName' => 'Anonymous', // Email for non-authenticated commits
+ 'anonymousEmail' => 'anonymous@phorkie', // Email for non-authenticated commits
+);
$GLOBALS['phorkie']['tools'] = array(
'\\phorkie\\Tool_Xmllint' => true,
'\\phorkie\\Tool_PHPlint' => true,
//$GLOBALS['phorkie']['cfg']['git']['private'] = 'ssh://git@bogo:paste/';
//$GLOBALS['phorkie']['cfg']['elasticsearch'] = 'http://localhost:9200/phorkie/';
//$GLOBALS['phorkie']['cfg']['setupcheck'] = false;
+
+//$GLOBALS['phorkie']['auth']['securityLevel'] = 0;
+//$GLOBALS['phorkie']['auth']['listedUsersOnly'] = false;
+//$GLOBALS['phorkie']['auth']['users'] = array(
+// 'https://www.google.com/accounts/o8/id?id=ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+// 'http://anonymous.phorkie.openid'
+//);
?>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="{{css}}"/>
- <link rel="stylesheet" href="/phorkie.css" />
+ <link rel="stylesheet" href="/css/phorkie.css" />
<title>{% block title %}{% endblock %} - {{title}}</title>
- <script src="/jquery-1.7.2.min.js"></script>
- <script src="/phorkie.js"></script>
+ <script src="/js/jquery-1.7.2.min.js"></script>
+ <script src="/js/phorkie.js"></script>
{% block meta %}{% endblock %}
</head>
<body>
</li>
{% if db.adapter %}
<li>
- <form class="navbar-search pull-left" action="/search" method="get">
+ <form class="navbar-search" action="/search" method="get">
<input type="text" class="search-query" name="q" placeholder="Search" value="{{query}}"/>
</form>
</li>
{% endif %}
</ul>
+ <ul class="nav pull-right">
+ {% if identity %}
+ <li>
+ <a href="/user">{{name}} ({{email}})</a>
+ </li>
+ <li>
+ <a href="/login?logout">Logout</a>
+ </li>
+ {% else %}
+ <li>
+ <a href="/login">Login</a>
+ </li>
+ {% endif %}
+ </ul>
</div>
</div>
</div>
<a href="//sf.net/p/phorkie/">phorkie</a>,
the self-hosted, git-based pastebin software is available under the
<a href="https://sf.net/p/phorkie/">
- <acronym title="GNU Affero General Public License">AGPL</acronym></a>.
+ <abbr title="GNU Affero General Public License">AGPL</abbr></a>.
</div>
</body>
-</html>
\ No newline at end of file
+</html>
{% else %}
<p>No commits yet</p>
{% endfor %}
-</ul>
\ No newline at end of file
+</ul>
<!DOCTYPE html>
<html>
<head>
- <link rel="stylesheet" href="phorkie.css" />
+ <link rel="stylesheet" href="/css/phorkie.css" />
<title>Error - phorkie</title>
</head>
<body>
--- /dev/null
+{% extends "base.htm" %}
+{% block title %}Access Denied{% endblock %}
+
+{% block content %}
+
+<h2>Access Denied</h2>
+{% if identity %}
+<p>
+ You are logged in with the following OpenID:
+</p>
+<p>
+ <code>{{identity}}</code>
+</p>
+<p>
+ Unfortunately, your OpenID is not unlocked.
+ Contact the site administrator to get access.
+</p>
+{% else %}
+<p>
+ We're sorry; but you have to <a href="/login">log in</a> to access this page.
+</p>
+{% endif %}
+{% endblock %}
+
--- /dev/null
+{% extends "base.htm" %}
+{% block title %}Login{% endblock %}
+
+{% block content %}
+
+<link rel="stylesheet" href="css/openid.css" />
+
+<form method="post" action="/login" id="openid_form">
+
+<fieldset>
+ <legend>Sign-in</legend>
+ <div id="openid_choice" style="display: block; ">
+ <p>Please choose your account provider</p>
+ <div id="openid_btns">
+ <a title="Google" href="/login?openid_url=https://www.google.com/accounts/o8/id" class="google openid_large_btn"></a>
+ <a title="Yahoo" href="/login?openid_url=http://yahoo.com/" class="yahoo openid_large_btn"></a>
+ </div>
+ <div id="openid_input_area">
+ <p>or enter your OpenID URL.</p>
+ <input id="openid_url" type="text" name="openid_url" value="http://" />
+ <input class="btn" id="openid_submit" type="submit" value="Sign in" />
+ </div>
+ </div>
+</fieldset>
+
+</form>
+{% endblock %}
{% endfor %}
</ul>
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
{% endif %}
</ul>
</div>
-{% endif %}
\ No newline at end of file
+{% endif %}
<p>
revision <strong>{{repo.hash}}</strong>
</p>
- <p>
- </p>
</div>
<div class="span2">
</div>
--- /dev/null
+{% extends "base.htm" %}
+{% block title %}User Preferences{% endblock %}
+
+{% block content %}
+
+<form method="post" action="/user" id="user_form">
+<fieldset>
+ <legend>User Profile</legend>
+ <p>Please update your git preferences.</p>
+ <p><label>OpenID:</label><code>{{ identity }}</code></p>
+ <label for='name'>Name:</label><input class="" id="name" type="text" name="name" width="35" value="{{ name }}"><br/>
+ <label for='email'>Email:</label><input class="" id="email" type="text" name="email" width="35" value="{{ email }}"><br/>
+ <input class="btn" id="submit" type="submit" value="Update">
+</fieldset>
+</form>
+{% endblock %}
}
}
-?>
\ No newline at end of file
+?>
}
}
-?>
\ No newline at end of file
+?>
}
}
-?>
\ No newline at end of file
+?>
*/
public $hash;
+ /**
+ * Commit message of the last (or current) revision
+ *
+ * @var string
+ */
+ public $message;
/**
$this->id = (int)$_GET['id'];
$this->loadDirs();
$this->loadHash();
+ $this->loadMessage();
}
protected function loadDirs()
$this->hash = $output;
}
+ /**
+ * Populates $this->message
+ *
+ * @return void
+ */
+ public function loadMessage()
+ {
+ $rev = (isset($this->hash)) ? $this->hash : 'HEAD';
+ $output = $this->getVc()->getCommand('log')
+ ->setOption('oneline')
+ ->addArgument('-1')
+ ->addArgument($rev)
+ ->execute();
+ $output = trim($output);
+ if (strpos($output, ' ') > 0) {
+ $output = substr($output, strpos($output, ' '), strlen($output));
+ $this->message = trim($output);
+ } else {
+ $this->message = "This commit message intentionally left blank.";
+ }
+ }
+
public function loadById($id)
{
if (!is_numeric($id)) {
}
}
-?>
\ No newline at end of file
+?>
*
* @return boolean True if the post was successful
*/
- public function process($postData)
+ public function process($postData, $sessionData)
{
if (!isset($postData['files'])) {
return false;
}
}
+ $commitmsg = "phorkie commit";
+
+ if (isset($sessionData['identity'])) {
+ $notes = $sessionData['identity'];
+ } else {
+ $notes = $sessionData['ipaddr'];
+ }
+
if ($bCommit) {
$vc->getCommand('commit')
- ->setOption('message', '')
- ->setOption('allow-empty-message')
- ->setOption('author', 'Anonymous <anonymous@phorkie>')
+ ->setOption('message', $commitmsg)
+ ->setOption('author', $sessionData['name'].' <'.$sessionData['email'].'>')
+ ->execute();
+ //FIXME: git needs ref BEFORE add. ideally VersionControl_Git needs to be updated
+ $vc->getCommand('notes --ref=identity add')
+ ->setOption('force')
+ ->setOption('message', "$notes")
->execute();
$bChanged = true;
}
}
}
-?>
\ No newline at end of file
+?>
public $annotations;
}
-?>
\ No newline at end of file
+?>
}
}
-?>
\ No newline at end of file
+?>
}
-?>
\ No newline at end of file
+?>
RewriteRule ^search$ /search.php
RewriteRule ^search/([0-9]+)$ /search.php?page=$1
+
+RewriteRule ^login$ /login.php
+RewriteRule ^user$ /user.php
--- /dev/null
+body {
+ font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+#openid_form {
+ width: 470px;
+}
+#openid_form legend {
+ font-weight: bold;
+}
+#openid_choice {
+ display: none;
+}
+#openid_input_area {
+ clear: both;
+}
+#openid_btns {
+ height: 66px;
+ margin-bottom: 10px;
+}
+#openid_btns br {
+ clear: both;
+}
+#openid_highlight {
+ padding: 3px;
+ background-color: #FFFCC9;
+ float: left;
+}
+#openid_url {
+ margin: 0px !important;
+ width: 250px;
+ background: #FFF url(/images/openid-inputicon.gif) no-repeat scroll 0 50%;
+ padding-left:18px;
+}
+.openid_large_btn {
+ width: 100px;
+ height: 60px;
+ border: 1px solid #DDD;
+ margin: 3px;
+ float: left;
+}
+.openid_small_btn {
+ width: 24px;
+ height: 24px;
+ border: 1px solid #DDD;
+ margin: 3px;
+ float: left;
+}
+.google {
+ background: #FFF url(/images/google.gif) no-repeat center center;
+}
+.yahoo {
+ background: #FFF url(/images/yahoo.gif) no-repeat center center;
+}
+
+a.openid_large_btn:hover {
+ outline: none;
+ border: 1px solid #030303;
+}
+a.openid_large_btn:focus {
+ -moz-outline-style: none;
+}
+.openid_selected {
+ border: 4px solid #DDD;
+}
/**
* Delete paste or ask for deletion
*/
+$reqWritePermissions = true;
require_once 'www-header.php';
$repo = new Repository();
/**
* Display paste contents
*/
+$reqWritePermissions = false;
require_once 'www-header.php';
$repo = new Repository();
* Display DOAP of the paste.
* Contains a machine-readable project description with Git URL.
*/
+$reqWritePermissions = false;
require_once 'www-header.php';
$repo = new Repository();
/**
* Edit paste contents
*/
+$reqWritePermissions = true;
require_once 'www-header.php';
$repo = new Repository();
$repo->loadFromRequest();
$repopo = new Repository_Post($repo);
-if ($repopo->process($_POST)) {
+if ($repopo->process($_POST, $_SESSION)) {
redirect($repo->getLink('display'));
}
--- /dev/null
+<?php
+namespace phorkie;
+/**
+ * Show an access denied error
+ */
+
+header('HTTP/1.0 403 Forbidden');
+render(
+ 'forbidden',
+ array(
+ 'identity' => isset($_SESSION['identity']) ? $_SESSION['identity'] : null
+ )
+);
+exit();
+?>
* Fork a repository
*/
namespace phorkie;
+$reqWritePermissions = true;
require_once 'www-header.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
//FIXME: where to put fork source link?
redirect($new->getLink('display'));
-?>
\ No newline at end of file
+?>
<?php
+/**
+ * Jump to the index as per the configuration
+ */
namespace phorkie;
+$reqWritePermissions = false;
require_once 'www-header.php';
-require_once $GLOBALS['phorkie']['cfg']['index'].".php";
+
+header(
+ 'Location: '
+ . Tools::fullUrl('/' . $GLOBALS['phorkie']['cfg']['index'])
+);
?>
jt.children('i').toggleClass('icon-chevron-down')
.toggleClass('icon-chevron-up');
jt.parents('.row-fluid').children('.additional').toggle(time);
-}
\ No newline at end of file
+}
<?php
/**
- * Fork a repository
+ * List a repository
*/
namespace phorkie;
+$reqWritePermissions = false;
require_once 'www-header.php';
$rs = new Repositories();
--- /dev/null
+<?php
+namespace phorkie;
+$noSecurityCheck = true;
+require_once 'www-header.php';
+
+if (isset($_REQUEST['logout'])) {
+ unset($_SESSION);
+ session_destroy();
+ header('Location: ' . Tools::fullUrl('/'));
+ exit();
+}
+
+if (!count($_GET) && !count($_POST)) {
+ render('login');
+ exit();
+}
+
+// Hackaround Non-Javascript Login Page
+if (!count($_POST) && isset($_GET['openid_url'])) {
+ $_POST = $_GET;
+}
+
+if (isset($_POST['openid_url'])) {
+ $openid_url = $_POST['openid_url'];
+} else if (isset($_SESSION['openid_url'])) {
+ $openid_url = $_SESSION['openid_url'];
+} else {
+ $openid_url = null;
+}
+
+$realm = Tools::fullUrl('/');
+$returnTo = Tools::fullUrl('/login');
+
+try {
+ $o = new \OpenID_RelyingParty($returnTo, $realm, $openid_url);
+} catch (OpenID_Exception $e) {
+ throw new Exception($e->getMessage());
+}
+
+if (!empty($_POST['disable_associations']) || !empty($_SESSION['disable_associations'])) {
+ $o->disableAssociations();
+ $_SESSION['disable_associations'] = true;
+}
+
+$log = new \OpenID_Observer_Log;
+\OpenID::attach($log);
+
+if (isset($_POST['openid_url'])) {
+
+ $_SESSION['openid_url'] = $openid_url;
+ try {
+ $authRequest = $o->prepare();
+ } catch (OpenID_Exception $e) {
+ throw new Exception($e->getMessage());
+ }
+
+ // SREG
+ $sreg = new \OpenID_Extension_SREG11(\OpenID_Extension::REQUEST);
+ $sreg->set('required', 'email,fullname');
+ $authRequest->addExtension($sreg);
+
+ // AX, http://stackoverflow.com/a/7657061/282601
+ $ax = new \OpenID_Extension_AX(\OpenID_Extension::REQUEST);
+ $ax->set('type.email', 'http://axschema.org/contact/email');
+ $ax->set('type.firstname', 'http://axschema.org/namePerson/first');
+ $ax->set('type.lastname', 'http://axschema.org/namePerson/last');
+ $ax->set('type.fullname', 'http://axschema.org/namePerson');
+ $ax->set('mode', 'fetch_request');
+ $ax->set('required', 'email,firstname,lastname,fullname');
+ $authRequest->addExtension($ax);
+
+ $url = $authRequest->getAuthorizeURL();
+
+ header("Location: $url");
+ exit;
+
+}
+
+if (isset($_SESSION['openid_url'])) {
+ $usid = $_SESSION['openid_url'];
+ unset($_SESSION['openid_url']);
+} else {
+ $usid = null;
+}
+
+unset($_SESSION['disable_associations']);
+
+if (!count($_POST)) {
+ list(, $queryString) = explode('?', $_SERVER['REQUEST_URI']);
+} else {
+ // I hate php sometimes
+ $queryString = file_get_contents('php://input');
+}
+
+$message = new \OpenID_Message($queryString, \OpenID_Message::FORMAT_HTTP);
+$id = $message->get('openid.claimed_id');
+$mode = $message->get('openid.mode');
+
+try {
+ $result = $o->verify(new \Net_URL2($returnTo . '?' . $queryString), $message);
+
+ if ($result->success()) {
+ $status = "<tr><td>Status:</td><td><font color='green'>SUCCESS!";
+ $status .= " ({$result->getAssertionMethod()})</font></td></tr>";
+ } else {
+ $status = "<tr><td>Status:</td><td><font color='red'>FAIL!";
+ $status .= " ({$result->getAssertionMethod()})</font></td></tr>";
+ }
+} catch (OpenID_Exception $e) {
+ $status = "<tr><td>Status:</td><td><font color='red'>EXCEPTION!";
+ $status .= " ({$e->getMessage()} : {$e->getCode()})</font></td></tr>";
+}
+
+
+$openid = $message->getArrayFormat();
+
+$email = isset($openid['openid.ext1.value.email'])
+ ? $openid['openid.ext1.value.email']
+ : null;
+$email = isset($openid['openid.ext2.value.email']) && !isset($email)
+ ? $openid['openid.ext2.value.email']
+ : $email;
+$email = isset($openid['openid.sreg.email']) && !isset($email)
+ ? $openid['openid.sreg.email']
+ : $email;
+$email = isset($openid['openid.ax.value.email'])
+ && isset($openid['openid.ax.type.email'])
+ && $openid['openid.ax.type.email'] == 'http://axschema.org/contact/email'
+ && !isset($email)
+ ? $openid['openid.ax.value.email']
+ : $email;
+$_SESSION['email'] = isset($email)
+ ? $email
+ : $GLOBALS['phorkie']['auth']['anonymousEmail'];
+
+$name = isset($openid['openid.ext1.value.firstname'])
+ && isset($openid['openid.ext1.value.lastname'])
+ ? $openid['openid.ext1.value.firstname'] . ' '
+ . $openid['openid.ext1.value.lastname']
+ : null;
+$name = isset($openid['openid.sreg.fullname']) && !isset($name)
+ ? $openid['openid.sreg.fullname']
+ : $name;
+$name = isset($openid['openid.ax.value.fullname'])
+ && isset($openid['openid.ax.type.fullname'])
+ && $openid['openid.ax.type.fullname'] == 'http://axschema.org/namePerson'
+ && !isset($name)
+ ? $openid['openid.ax.value.fullname']
+ : $name;
+
+$_SESSION['name'] = isset($name) ? $name : $_SERVER['REMOTE_ADDR'];
+$_SESSION['identity'] = $openid['openid.identity'];
+
+if (isset($_SESSION['REQUEST_URI'])) {
+ $redirect = Tools::fullUrl($_SESSION['REQUEST_URI']);
+} else {
+ $redirect = Tools::fullUrl('/');
+}
+header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
+exit;
+?>
*
* Creates and redirects to display page
*/
+$reqWritePermissions = true;
require_once 'www-header.php';
$repopo = new Repository_Post();
-if ($repopo->process($_POST)) {
+if ($repopo->process($_POST, $_SESSION)) {
redirect($repopo->repo->getLink('display'));
}
<?php
+namespace phorkie;
/**
* Displays a file
*/
-namespace phorkie;
+$reqWritePermissions = false;
require_once 'www-header.php';
+
$repo = new Repository();
$repo->loadFromRequest();
<?php
namespace phorkie;
/**
- * Display paste contents
+ * Display historic paste contents
*/
+$reqWritePermissions = false;
require_once 'www-header.php';
$repo = new Repository();
/**
* Search for a search term
*/
+$reqWritePermissions = false;
require_once 'www-header.php';
if (!isset($_GET['q']) || $_GET['q'] == '') {
)
);
-?>
\ No newline at end of file
+?>
--- /dev/null
+<?php
+/**
+ * Edit user information
+ */
+namespace phorkie;
+$reqWritePermissions = true;
+require_once 'www-header.php';
+
+if (isset($_POST['name'])) {
+ $_SESSION['name'] = substr(filter_var($_POST['name'], FILTER_SANITIZE_STRING), 0, 35);
+}
+
+if (isset($_POST['email'])) {
+ $_SESSION['email'] = substr(filter_var($_POST['email'], FILTER_SANITIZE_EMAIL), 0, 35);
+}
+
+render(
+ 'user',
+ array(
+ 'identity' => $_SESSION['identity'],
+ 'name' => $_SESSION['name'],
+ 'email' => $_SESSION['email']
+ )
+);
+?>
<?php
namespace phorkie;
+session_start();
+
set_include_path(
__DIR__ . '/../src/'
. PATH_SEPARATOR . get_include_path()
if ($GLOBALS['phorkie']['cfg']['setupcheck']) {
SetupCheck::run();
}
+
+// Set/Get git commit session variables
+$_SESSION['ipaddr'] = $_SERVER['REMOTE_ADDR'];
+if (!isset($_SESSION['name'])) {
+ $_SESSION['name'] = $GLOBALS['phorkie']['auth']['anonymousName'];
+}
+if (!isset($_SESSION['email'])) {
+ $_SESSION['email'] = $GLOBALS['phorkie']['auth']['anonymousEmail'];
+}
+
\Twig_Autoloader::register();
$loader = new \Twig_Loader_Filesystem($GLOBALS['phorkie']['cfg']['tpl']);
);
//$twig->addExtension(new \Twig_Extension_Debug());
-function render($tplname, $vars)
+if (!isset($noSecurityCheck) || $noSecurityCheck !== true) {
+ require __DIR__ . '/www-security.php';
+}
+
+function render($tplname, $vars = array())
{
$vars['css'] = $GLOBALS['phorkie']['cfg']['css'];
$vars['title'] = $GLOBALS['phorkie']['cfg']['title'];
$vars['topbar'] = $GLOBALS['phorkie']['cfg']['topbar'];
+ if (isset($_SESSION['identity'])) {
+ $vars['identity'] = $_SESSION['identity'];
+ $vars['name'] = $_SESSION['name'];
+ $vars['email'] = $_SESSION['email'];
+ }
$vars['db'] = new Database();
$template = $GLOBALS['twig']->loadTemplate($tplname . '.htm');
header('Location: ' . $target);
exit();
}
-?>
\ No newline at end of file
+?>
--- /dev/null
+<?php
+namespace phorkie;
+/**
+ * security levels + login requirement:
+ */
+
+if (!isset($GLOBALS['phorkie']['auth']['securityLevel'])) {
+ //not set? highest level of security
+ $GLOBALS['phorkie']['auth']['securityLevel'] = 2;
+}
+
+if ($GLOBALS['phorkie']['auth']['securityLevel'] == 0) {
+ //everyone may do everything
+ return;
+}
+
+$logged_in = false;
+if (!isset($_SESSION['identity'])) {
+ //not logged in
+} else if ($GLOBALS['phorkie']['auth']['listedUsersOnly']) {
+ if (in_array($_SESSION['identity'], $GLOBALS['phorkie']['auth']['users'])) {
+ $logged_in = true;
+ }
+} else {
+ //session identity exists, no special checks required
+ $logged_in = true;
+}
+
+if ($logged_in) {
+ //you may do everything if you're logged in
+ return;
+}
+
+if (!isset($reqWritePermissions)) {
+ $reqWritePermissions = true;
+}
+if ($GLOBALS['phorkie']['auth']['securityLevel'] == 1
+ && !$reqWritePermissions
+) {
+ return;
+}
+
+$_SESSION['REQUEST_URI'] = $_SERVER['REQUEST_URI'];
+require 'forbidden.php';
+?>