Commit 67a347ba authored by Evan Prodromou's avatar Evan Prodromou

considerable coding

darcs-hash:20080514145436-84dde-d0994cb35d3fe8545d3f08abeec3cdfe7559c67d.gz
parent f0a30cc8
+ login
+ register
- settings
+ disallow login if user is logged in
+ disallow register if user is logged in
+ common_current_user()
+ common_logged_in()
+ session variable for login
+ post notice
+ logout
+ subscribe
+ unsubscribe
+ subscribe links on profile
- licenses
- header menu
- footer menu
- disallow direct to PHP files
- common_local_url()
- configuration system ($config)
- RSS 1.0 feeds of a user's notices
- RSS 1.0 dump of a user's notices
- RSS 1.0 feed of all public notices
- RDF dump of entire site
- FOAF dump for user
- delete a notice
- make sure canonical usernames are unique
- upload avatar
- design from Open Source Web Designs
- release 0.1
- gettext
- subscribe remote
- add subscriber remote
- send remote notice
- receive remote notice
- confirmation email
- tinyurl-ification of URLs
- jQuery for as much as possible
- themes
- release 0.2
- @ messages
- # tags
- L: location
- stay logged in between sessions
- use RSS as a subscription
- URL notices
- image notices
- video notices
- audio notices
- release 0.3
- forward notices to Jabber
- forward notices to other IM
- forward notices to mobile phone
- machine tags
- release 0.4
- include twitter subscriptions
- include Pownce subscriptions
- privacy
- Wrap DB_DataObject with memcached caching layer
- login throttle to prevent brute-force attacks
- form token in login to prevent XSS
- release 1.0
- Atom Publishing Protocol
......@@ -11,6 +11,7 @@
/main/login login to site
/main/register register to site
/main/settings change account settings
/main/recover recover password
/doc/ documentation
about about this site
......
<?php
function handle_login() {
if ($_REQUEST['METHOD'] == 'POST') {
if (login_check_user($_REQUEST['user'], $_REQUEST['password'])) {
class LoginAction extends Action {
function handle($args) {
parent::handle($args);
if (common_logged_in()) {
common_user_error(_t('Already logged in.'));
} else if ($this->arg('METHOD') == 'POST') {
$this->check_login();
} else {
$this->show_form();
}
} else {
if (user_logged_in()) {
}
function check_login() {
# XXX: form token in $_SESSION to prevent XSS
# XXX: login throttle
$nickname = $this->arg('nickname');
$password = $this->arg('password');
if (common_check_user($nickname, $password)) {
common_set_user($nickname);
common_redirect(common_local_url('all',
array('nickname' =>
$nickname)));
} else {
login_show_form();
$this->show_form(_t('Incorrect username or password.'));
}
}
}
function login_show_form() {
html_start();
html_head("Login");
html_body();
function show_form($error=NULL) {
common_show_header(_t('Login'));
if (!is_null($error)) {
common_element('div', array('class' => 'error'), $msg);
}
common_start_element('form', array('method' => 'POST',
'id' => 'login',
'action' => common_local_url('login')));
common_element('label', array('for' => 'username'),
_t('Name'));
common_element('input', array('name' => 'username',
'type' => 'text',
'id' => 'username'));
common_element('label', array('for' => 'password'),
_t('Password'));
common_element('input', array('name' => 'password',
'type' => 'password',
'id' => 'password'));
common_element('input', array('name' => 'submit',
'type' => 'submit',
'id' => 'submit'),
_t('Login'));
common_element('input', array('name' => 'cancel',
'type' => 'button',
'id' => 'cancel'),
_t('Cancel'));
}
}
function login_check_user($username, $password) {
}
\ No newline at end of file
<?php
class LogoutAction extends Action {
function handle($args) {
parent::handle($args);
if (!common_logged_in()) {
common_user_error(_t('Not logged in.'));
} else {
common_set_user(NULL);
common_redirect(common_local_url('main'));
}
}
}
<?php
class NewnoticeAction extends Action {
function handle($args) {
parent::handle($args);
# XXX: Ajax!
if (!common_logged_in()) {
common_user_error(_t('Not logged in.'));
} else if ($this->arg('METHOD') == 'POST') {
if ($this->save_new_notice()) {
# XXX: smarter redirects
$user = common_current_user();
assert(!is_null($user)); # see if... above
# XXX: redirect to source
# XXX: use Ajax instead of a redirect
common_redirect(common_local_url('all',
array('nickname' =>
$user->nickname)));
} else {
common_server_error(_t('Problem saving notice.'));
}
} else {
$this->show_form();
}
}
function save_new_notice() {
$user = common_current_user();
assert($user); # XXX: maybe an error instead...
$notice = DB_DataObject::factory('notice');
assert($notice);
$notice->profile_id = $user->id; # user id *is* profile id
$notice->content = $this->arg('content');
$notice->created = time();
return $notice->insert();
}
function show_form() {
common_start_element('form', array('id' => 'newnotice', 'method' => 'POST',
'action' => common_local_url('newnotice')));
common_element('span', 'nickname', $profile->nickname);
common_element('textarea', array('rows' => 4, 'cols' => 80, 'id' => 'content'));
common_element('input', array('type' => 'submit'), 'Send');
common_end_element('form');
}
}
\ No newline at end of file
<?php
class RegisterAction extends Action {
function handle($args) {
parent::handle($args);
if (common_logged_in()) {
common_user_error(_t('Already logged in.'));
} else if ($this->arg('METHOD') == 'POST') {
$this->try_register();
} else {
$this->show_form();
}
}
function try_register() {
$nickname = $this->arg('nickname');
$password = $this->arg('password');
$confirm = $this->arg('confirm');
$email = $this->arg('email');
# Input scrubbing
$nickname = common_canonical_nickname($nickname);
$email = common_canonical_email($email);
if ($this->nickname_exists($nickname)) {
$this->show_form(_t('Username already exists.'));
} else if ($this->email_exists($email)) {
$this->show_form(_t('Email address already exists.'));
} else if ($password != $confirm) {
$this->show_form(_t('Passwords don\'t match.'));
} else if ($this->register_user($nickname, $password, $email)) {
common_set_user($nickname);
common_redirect(common_local_url('settings'));
} else {
$this->show_form(_t('Invalid username or password.'));
}
}
# checks if *CANONICAL* nickname exists
function nickname_exists($nickname) {
$user = User::staticGet('nickname', $nickname);
return ($user !== false);
}
# checks if *CANONICAL* email exists
function email_exists($email) {
$email = common_canonicalize_email($email);
$user = User::staticGet('email', $email);
return ($user !== false);
}
function register_user($nickname, $password, $email) {
# TODO: wrap this in a transaction!
$profile = new Profile();
$profile->nickname = $nickname;
$profile->created = time();
$id = $profile->insert();
if (!$id) {
return FALSE;
}
$user = new User();
$user->id = $id;
$user->nickname = $nickname;
$user->password = common_munge_password($password, $id);
$user->email = $email;
$user->created = time();
$result = $user->insert();
if (!$result) {
# Try to clean up...
$profile->delete();
}
return $result;
}
function show_form($error=NULL) {
common_show_header(_t('Login'));
common_start_element('form', array('method' => 'POST',
'id' => 'login',
'action' => common_local_url('login')));
common_element('label', array('for' => 'username'),
_t('Name'));
common_element('input', array('name' => 'username',
'type' => 'text',
'id' => 'username'));
common_element('label', array('for' => 'password'),
_t('Password'));
common_element('input', array('name' => 'password',
'type' => 'password',
'id' => 'password'));
common_element('label', array('for' => 'confirm'),
_t('Confirm'));
common_element('input', array('name' => 'confirm',
'type' => 'password',
'id' => 'confirm'));
common_element('label', array('for' => 'email'),
_t('Email'));
common_element('input', array('name' => 'email',
'type' => 'text',
'id' => 'email'));
common_element('input', array('name' => 'submit',
'type' => 'submit',
'id' => 'submit'),
_t('Login'));
common_element('input', array('name' => 'cancel',
'type' => 'button',
'id' => 'cancel'),
_t('Cancel'));
}
}
<?php
class SettingsAction extends Action {
function handle($args) {
parent::handle($args);
if ($this->arg('METHOD') == 'POST') {
$nickname = $this->arg('nickname');
$fullname = $this->arg('fullname');
$email = $this->arg('email');
$homepage = $this->arg('homepage');
$bio = $this->arg('bio');
$location = $this->arg('location');
$oldpass = $this->arg('oldpass');
$password = $this->arg('password');
$confirm = $this->arg('confirm');
if ($password) {
if ($password != $confirm) {
$this->show_form(_t('Passwords don\'t match.'));
}
} else if (
$error = $this->save_settings($nickname, $fullname, $email, $homepage,
$bio, $location, $password);
if (!$error) {
$this->show_form(_t('Settings saved.'), TRUE);
} else {
$this->show_form($error);
}
} else {
$this->show_form();
}
\ No newline at end of file
......@@ -37,7 +37,8 @@ class ShownoticeAction extends Action {
'class' => 'nickname'),
$profile->nickname);
# FIXME: URL, image, video, audio
common_element('span', array('class' => 'content'), $notice->content);
common_element('span', array('class' => 'content'),
$notice->content);
common_element('span', array('class' => 'date'),
common_date_string($notice->created));
common_end_element('div');
......
......@@ -9,34 +9,43 @@ class ShowstreamAction extends StreamAction {
parent::handle($args);
$nickname = $this->arg('profile');
$profile = Profile::staticGet('nickname', strtolower($nickname));
if (!$profile) {
$this->no_such_user();
}
$user = User::staticGet($profile->id);
$nickname = common_canonicalize_nickname($this->arg('profile'));
$user = User::staticGet('nickname', $nickname);
if (!$user) {
// remote profile
$this->no_such_user();
}
$profile = $user->getProfile();
if (!$profile) {
common_server_error(_t('User record exists without profile.'));
}
# Looks like we're good; show the header
common_show_header($profile->nickname);
$cur = common_current_user();
if ($profile->id == current_user()->id) {
if ($cur && $profile->id == $cur->id) {
$this->notice_form();
}
$this->show_profile($profile);
$this->show_last_notice($profile);
if ($cur) {
if ($cur->isSubscribed($profile)) {
$this->show_unsubscribe_form($profile);
} else {
$this->show_subscribe_form($profile);
}
}
$this->show_statistics($profile);
$this->show_subscriptions($profile);
$this->show_notices($profile);
......@@ -75,13 +84,33 @@ class ShowstreamAction extends StreamAction {
common_element('div', 'bio', $profile->bio);
}
}
function show_subscribe_form($profile) {
common_start_element('form', array('id' => 'subscribe', 'method' => 'POST',
'action' => common_local_url('subscribe')));
common_element('input', array('id' => 'subscribeto',
'name' => 'subscribeto',
'type' => 'hidden',
'value' => $profile->nickname));
common_element('input', array('type' => 'submit'), _t('subscribe'));
common_end_element('form');
}
function show_unsubscribe_form($profile) {
common_start_element('form', array('id' => 'unsubscribe', 'method' => 'POST',
'action' => common_local_url('unsubscribe')));
common_element('input', array('id' => 'unsubscribeto',
'name' => 'unsubscribeto',
'type' => 'hidden',
'value' => $profile->nickname));
common_element('input', array('type' => 'submit'), _t('unsubscribe'));
common_end_element('form');
}
function show_subscriptions($profile) {
# XXX: add a limit
# XXX: add a limit
$subs = $profile->getLink('id', 'subscription', 'subscriber');
common_start_element('div', 'subscriptions');
$cnt = 0;
......@@ -113,7 +142,7 @@ class ShowstreamAction extends StreamAction {
array('profile' => $profile->nickname))
'class' => 'moresubscriptions'),
_t('All subscriptions'));
common_end_element('div');
}
......@@ -174,7 +203,8 @@ class ShowstreamAction extends StreamAction {
while ($notice->fetch()) {
# FIXME: URL, image, video, audio
common_element('span', array('class' => 'content'), $notice->content);
common_element('span', array('class' => 'content'),
$notice->content);
common_element('span', array('class' => 'date'),
common_date_string($notice->created));
}
......
<?php
class SubscribeAction extends Action {
function handle($args) {
parent::handle($args);
if (!common_logged_in()) {
common_user_error(_t('Not logged in.'));
return;
}
$other_nickname = $this->arg('subscribeto');
$other = User::staticGet('nickname', $other_nickname);
if (!$other) {
common_user_error(_t('No such user.'));
return;
}
$user = common_current_user();
if ($user->isSubscribed($other)) {
common_user_error(_t('Already subscribed!.'));
return;
}
$sub = new Subscription();
$sub->subscriber = $user->id;
$sub->subscribed = $other->id;
$sub->created = time();
if (!$sub->insert()) {
common_server_error(_t('Couldn\'t create subscription.'));
return;
}
common_redirect(common_local_url('all', array('nickname' =>
$user->nickname)));
}
}
\ No newline at end of file
<?php
class UnsubscribeAction extends Action {
function handle($args) {
parent::handle($args);
if (!common_logged_in()) {
common_user_error(_t('Not logged in.'));
return;
}
$other_nickname = $this->arg('unsubscribeto');
$other = User::staticGet('nickname', $other_nickname);
if (!$other) {
common_user_error(_t('No such user.'));
return;
}
$user = common_current_user();
if (!$user->isSubscribed($other)) {
common_server_error(_t('Not subscribed!.'));
}
$sub = new Subscription();
$sub->subscriber = $user->id;
$sub->subscribed = $other->id;
if (!$sub->delete()) {
common_server_error(_t('Couldn\'t delete subscription.'));
return;
}
common_redirect(common_local_url('all', array('nickname' =>
$user->nickname)));
}
}
......@@ -21,4 +21,16 @@ class User extends DB_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
function getProfile() {
return Profile::staticGet($this->$id);
}
function isSubscribed($other) {
assert(!is_null($other));
$sub = DB_DataObject::factory('subscription');
$sub->subscriber = $this->id;
$sub->subscribed = $other->id;
return $sub->find();
}
}
......@@ -5,7 +5,7 @@
// default configuration, overwritten in config.php
$config =
array('site' =>
array('site' =>
array('name' => 'Just another µB'),
'dsn' =>
array('phptype' => 'mysql',
......@@ -20,20 +20,7 @@ $config =
require_once(INSTALLDIR . '/config.php');
require_once('DB.php');
function common_database() {
global $config;
$db =& DB::connect($config['dsn'], $config['dboptions']);
if (PEAR::isError($db)) {
common_server_error($db->getMessage());
} else {
return $db;
}
}
function common_read_database() {
// XXX: read from slave server
return common_database();
}
# Show a server error
function common_server_error($msg) {
header('Status: 500 Server Error');
......@@ -43,12 +30,14 @@ function common_server_error($msg) {
exit();
}
function common_user_error($msg) {
# Show a user error
function common_user_error($msg, $code=200) {
common_show_header('Error');
common_element('div', array('class' => 'error'), $msg);
common_show_footer();
}
# Start an HTML element
function common_element_start($tag, $attrs=NULL) {
print "<$tag";
if (is_array($attrs)) {
......@@ -67,7 +56,7 @@ function common_element_end($tag) {
function common_element($tag, $attrs=NULL, $content=NULL) {
common_element_start($tag, $attrs);
if ($content) print $content;
if ($content) print htmlspecialchars($content);
common_element_end($tag);
}
......@@ -75,7 +64,8 @@ function common_show_header($pagetitle) {
global $config;
common_element_start('html');
common_element_start('head');
common_element('title', NULL, $pagetitle . " - " . $config['site']['name']);
common_element('title', NULL,
$pagetitle . " - " . $config['site']['name']);
common_element_end('head');
common_element_start('body');
}
......@@ -85,6 +75,82 @@ function common_show_footer() {
common_element_end('html');
}
// TODO: set up gettext
# salted, hashed passwords are stored in the DB
function common_munge_password($id, $password) {
return md5($id . $password);
}
# check if a username exists and has matching password
function common_check_user($nickname, $password) {
$user = User::staticGet('nickname', $nickname);
if (is_null($user)) {
return false;
} else {
return (0 == strcmp(common_munge_password($password, $user->id),
$user->password));
}
}
# is the current user logged in?
function common_logged_in() {
return (!is_null(common_current_user()));
}
function common_have_session() {
return (0 != strcmp(session_id(), ''));
}
function common_ensure_session() {
if (!common_have_session()) {
@session_start();
}
}
function common_set_user($nickname) {
if (is_null($nickname) && common_have_session()) {
unset($_SESSION['userid']);
return true;
} else {
$user = User::staticGet('nickname', $nickname);
if ($user) {
common_ensure_session();
$_SESSION['userid'] = $user->id;
return true;
} else {
return false;
}
}
return false;
}
<