diff options
-rw-r--r-- | include/Contact.php | 79 | ||||
-rwxr-xr-x | include/text.php | 2 | ||||
-rw-r--r-- | mod/connections.php | 1 | ||||
-rw-r--r-- | mod/magic.php | 108 | ||||
-rw-r--r-- | mod/nogroup.php | 65 | ||||
-rw-r--r-- | mod/post.php | 244 | ||||
-rw-r--r-- | version.inc | 2 | ||||
-rwxr-xr-x | view/tpl/group_side.tpl | 5 |
8 files changed, 143 insertions, 363 deletions
diff --git a/include/Contact.php b/include/Contact.php index fcc5019e7..5725e06f0 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -489,62 +489,6 @@ function unmark_for_death($contact) { ); }} -if(! function_exists('contact_photo_menu')){ -function contact_photo_menu($contact) { - - $a = get_app(); - - $contact_url=""; - $pm_url=""; - $status_link=""; - $photos_link=""; - $posts_link=""; - $poke_link=""; - - $sparkle = false; - if($contact['xchan_network'] === NETWORK_ZOT) { - $sparkle = true; - $profile_link = $a->get_baseurl() . '/magic?f=&id=' . $contact['abook_id']; - } - else - $profile_link = $contact['xchan_url']; - - if($sparkle) { - $status_link = $profile_link . "&url=status"; - $photos_link = $profile_link . "&url=photos"; - $profile_link = $profile_link . "&url=profile"; - $pm_url = $a->get_baseurl() . '/message/new/' . $contact['xchan_hash']; - } - - $poke_link = $a->get_baseurl() . '/poke/?f=&c=' . $contact['abook_id']; - $contact_url = $a->get_baseurl() . '/connections/' . $contact['abook_id']; - $posts_link = $a->get_baseurl() . '/network/?cid=' . $contact['abook_id']; - - $menu = Array( - t("Poke") => $poke_link, - t("View Status") => $status_link, - t("View Profile") => $profile_link, - t("View Photos") => $photos_link, - t("Network Posts") => $posts_link, - t("Edit Contact") => $contact_url, - t("Send PM") => $pm_url, - ); - - - $args = array('contact' => $contact, 'menu' => &$menu); - - call_hooks('contact_photo_menu', $args); - - $o = ""; - foreach($menu as $k=>$v){ - if ($v!="") { - $o .= "<li><a href=\"$v\">$k</a></li>\n"; - } - } - return $o; -}} - - function random_profile() { $r = q("select xchan_url from xchan where 1 order by rand() limit 1"); if($r) @@ -553,26 +497,3 @@ function random_profile() { } -function contacts_not_grouped($uid,$start = 0,$count = 0) { - - if(! $count) { - $r = q("select count(*) as total from contact where uid = %d and self = 0 and id not in (select distinct(`contact-id`) from group_member where uid = %d) ", - intval($uid), - intval($uid) - ); - - return $r; - - - } - - $r = q("select * from contact where uid = %d and self = 0 and id not in (select distinct(`contact-id`) from group_member where uid = %d) and blocked = 0 and pending = 0 limit %d, %d", - intval($uid), - intval($uid), - intval($start), - intval($count) - ); - - return $r; -} - diff --git a/include/text.php b/include/text.php index 3d15a5c6b..ff695062f 100755 --- a/include/text.php +++ b/include/text.php @@ -1084,7 +1084,7 @@ function format_categories(&$item,$writeable) { if(! trim($term)) continue; $removelink = (($writeable) ? z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) : ''); - $categories[] = array('term' => $term, 'writeable' => $writeable, 'removelink' => $removelink, 'url' => $t['url']); + $categories[] = array('term' => $term, 'writeable' => $writeable, 'removelink' => $removelink, 'url' => zid($t['url'])); } } $s = replace_macros(get_markup_template('item_categories.tpl'),array( diff --git a/mod/connections.php b/mod/connections.php index 39bef0209..6b3ed113c 100644 --- a/mod/connections.php +++ b/mod/connections.php @@ -669,7 +669,6 @@ function connections_content(&$a) { $contacts[] = array( 'img_hover' => sprintf( t('%1$s [%2$s]'),$rr['xchan_name'],$rr['xchan_url']), 'edit_hover' => t('Edit contact'), - 'photo_menu' => contact_photo_menu($rr), 'id' => $rr['abook_id'], 'alt_text' => $alt_text, 'dir_icon' => $dir_icon, diff --git a/mod/magic.php b/mod/magic.php index 6c8ad24ed..a268f1ecb 100644 --- a/mod/magic.php +++ b/mod/magic.php @@ -13,64 +13,20 @@ function magic_init(&$a) { $dest = ((x($_REQUEST,'dest')) ? $_REQUEST['dest'] : ''); $rev = ((x($_REQUEST,'rev')) ? intval($_REQUEST['rev']) : 0); - if($hash) { - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where hubloc_hash = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($hash), - intval(HUBLOC_FLAGS_PRIMARY) - ); - } - elseif($addr) { - $x = q("select hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($addr), - intval(HUBLOC_FLAGS_PRIMARY) - ); - } - else { - - // See if we know anybody at the dest site that will unlock the door for us - // This is the equivalent of buzzing every apartment in an apartment block - // to get inside the front gate. The thing about magic auth is that we're - // authenticating to the other site. Permissions provided by various - // channels will still affect what we can do once authenticated. - - $b = explode('/',$dest); - - if(count($b) >= 2) { - $u = $b[0] . '//' . $b[2]; - - if(local_user()) { - // first look for a connection or anybody who knows us - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - left join abook on abook_xchan = hubloc_hash - where abook_channel = %d and hubloc_url = '%s' order by hubloc_id desc limit 5", - intval(local_user()), - dbesc($u) - ); - } - if(! $x) { - // no luck - ok anybody will do - $x = q("select xchan.xchan_url, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where hubloc_url = '%s' order by hubloc_id desc limit 5", - dbesc($u) - ); - } - if($x) { - // They must have a valid hubloc_addr - while(! strpos($x[0]['hubloc_addr'],'@')) { - array_shift($x); - } - } + $parsed = parse_url($dest); + if(! $parsed) + goaway($dest); + $basepath = $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : ''); - } - } - + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", + dbesc($basepath) + ); + if(! $x) { - // Finger them if they've never been seen here before + // Somebody new? Finger them if they've never been seen here before if($addr) { $ret = zot_finger($addr,null); @@ -78,10 +34,11 @@ function magic_init(&$a) { $j = json_decode($ret['body'],true); if($j) import_xchan($j); - $x = q("select hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and (hubloc_flags & %d) order by hubloc_id desc limit 1", - dbesc($addr), - intval(HUBLOC_FLAGS_PRIMARY) + + // Now try again + + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", + dbesc($basepath) ); } } @@ -91,8 +48,8 @@ function magic_init(&$a) { if($rev) goaway($dest); else { - logger('mod_magic: channel not found.' . print_r($_REQUEST,true)); - notice( t('Channel not found.') . EOL); + logger('mod_magic: no channels found for requested hub.' . print_r($_REQUEST,true)); + notice( t('Hub not found.') . EOL); return; } } @@ -112,25 +69,10 @@ function magic_init(&$a) { if(! $arr['proceed']) goaway($dest); - if($x[0]['hubloc_url'] === z_root()) { - $webbie = substr($x[0]['hubloc_addr'],0,strpos('@',$x[0]['hubloc_addr'])); - switch($dest) { - case 'channel': - $desturl = z_root() . '/channel/' . $webbie; - break; - case 'photos': - $desturl = z_root() . '/photos/' . $webbie; - break; - case 'profile': - $desturl = z_root() . '/profile/' . $webbie; - break; - default: - $desturl = $dest; - break; - } + if((get_observer_hash()) && ($x[0]['hubloc_url'] === z_root())) { // We are already authenticated on this site and a registered observer. // Just redirect. - goaway($desturl); + goaway($dest); } if(local_user()) { @@ -142,20 +84,15 @@ function magic_init(&$a) { $channel['token'] = $token; $channel['token_sig'] = $token_sig; - - $recip = array(array('guid' => $x[0]['hubloc_guid'],'guid_sig' => $x[0]['hubloc_guid_sig'])); - - $hash = random_string(); - $r = q("insert into verify ( type, channel, token, meta, created) values ('%s','%d','%s','%s','%s')", dbesc('auth'), intval($channel['channel_id']), dbesc($token), - dbesc($x[0]['hubloc_hash']), + dbesc($x[0]['hubloc_url']), dbesc(datetime_convert()) ); - $target_url = $x[0]['hubloc_callback'] . '/' . substr($x[0]['hubloc_addr'],0,strpos($x[0]['hubloc_addr'],'@')) ; + $target_url = $x[0]['hubloc_callback']; logger('mod_magic: redirecting to: ' . $target_url, LOGGER_DEBUG); goaway($target_url @@ -163,7 +100,6 @@ function magic_init(&$a) { . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION); } - if(strpos($dest,'/')) - goaway($dest); - goaway(z_root()); + goaway($dest); + } diff --git a/mod/nogroup.php b/mod/nogroup.php deleted file mode 100644 index 31ccaadbf..000000000 --- a/mod/nogroup.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -require_once('include/Contact.php'); -require_once('include/socgraph.php'); -require_once('include/contact_selectors.php'); - -function nogroup_init(&$a) { - - if(! local_user()) - return; - - require_once('include/group.php'); - require_once('include/contact_widgets.php'); - - if(! x($a->page,'aside')) - $a->page['aside'] = ''; - - $a->page['aside'] .= group_side('contacts','group',false,0,$contact_id); -} - - -function nogroup_content(&$a) { - - if(! local_user()) { - notice( t('Permission denied.') . EOL); - return ''; - } - - require_once('include/Contact.php'); - $r = contacts_not_grouped(local_user()); - if(count($r)) { - $a->set_pager_total($r[0]['total']); - } - $r = contacts_not_grouped(local_user(),$a->pager['start'],$a->pager['itemspage']); - if(count($r)) { - foreach($r as $rr) { - - - $contacts[] = array( - 'img_hover' => sprintf( t('Visit %s\'s profile [%s]'),$rr['name'],$rr['url']), - 'edit_hover' => t('Edit contact'), - 'photo_menu' => contact_photo_menu($rr), - 'id' => $rr['id'], - 'alt_text' => $alt_text, - 'dir_icon' => $dir_icon, - 'thumb' => $rr['thumb'], - 'name' => $rr['name'], - 'username' => $rr['name'], - 'sparkle' => $sparkle, - 'itemurl' => $rr['url'], - 'link' => $url, - 'network' => network_to_name($rr['network']), - ); - } - } - $tpl = get_markup_template("nogroup-template.tpl"); - $o .= replace_macros($tpl,array( - '$header' => t('Contacts who are not members of a group'), - '$contacts' => $contacts, - '$paginate' => paginate($a), - )); - - return $o; - -} diff --git a/mod/post.php b/mod/post.php index 2422afa8c..a7143aaf1 100644 --- a/mod/post.php +++ b/mod/post.php @@ -18,27 +18,26 @@ function post_init(&$a) { * Magic Auth * ========== * - * So-called "magic auth" takes place by a special exchange. On the remote computer, a redirection is made to the zot endpoint with special GET parameters. + * So-called "magic auth" takes place by a special exchange. On the site where the "channel to be authenticated" lives (e.g. $mysite), + * a redirection is made via $mysite/magic to the zot endpoint of the remote site ($remotesite) with special GET parameters. * - * Endpoint: https://example.com/post/name (name is now optional - we are authenticating to a site, not a channel) + * The endpoint is typically https://$remotesite/post - or whatever was specified as the callback url in prior communications + * (we will bootstrap an address and fetch a zot info packet if possible where no prior communications exist) * - * where 'name' is the left hand side of the channel webbie, for instance 'mike' where the webbie is 'mike@zothub.com' + * Four GET parameters are supplied: * - * Additionally four GET parameters are supplied: - * - ** auth => the webbie of the person requesting access + ** auth => the urlencoded webbie (channel@host.domain) of the channel requesting access ** dest => the desired destination URL (urlencoded) - ** sec => a random string which is also stored locally for use during the verification phase. + ** sec => a random string which is also stored on $mysite for use during the verification phase. ** version => the zot revision * - * When this packet is received, a zot message is sent to the site hosting the request auth identity. + * When this packet is received, an "auth-check" zot message is sent to $mysite. * (e.g. if $_GET['auth'] is foobar@podunk.edu, a zot packet is sent to the podunk.edu zot endpoint, which is typically /post) * If no information has been recorded about the requesting identity a zot information packet will be retrieved before * continuing. * - * The sender of this packet is the name attached to the request endpoint. e.g. 'mike' in this example. If this channel - * cannot be located, we will choose any local channel as the sender. The recipients will be a single recipient corresponding - * to the guid and guid_sig we have associated with the auth identity + * The sender of this packet is a random site channel. The recipients will be a single recipient corresponding + * to the guid and guid_sig we have associated with the requesting auth identity * * * { @@ -72,63 +71,35 @@ function post_init(&$a) { * } * * 'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the - * base64url encoded whirlpool hash of the source guid and guid_sig; signed with the source channel private key. + * base64url encoded whirlpool hash of the requestor's guid and guid_sig; signed with the source channel private key. * This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful * verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login. * * * */ - - if(argc() > 1) { - $webbie = argv(1); - } - else - $webbie = ''; if(array_key_exists('auth',$_REQUEST)) { logger('mod_zot: auth request received.'); $address = $_REQUEST['auth']; - $dest = $_REQUEST['dest']; + $desturl = $_REQUEST['dest']; $sec = $_REQUEST['sec']; $version = $_REQUEST['version']; - switch($dest) { - case 'channel': - $desturl = z_root() . '/channel/' . $webbie; - break; - case 'photos': - $desturl = z_root() . '/photos/' . $webbie; - break; - case 'profile': - $desturl = z_root() . '/profile/' . $webbie; - break; - default: - $desturl = $dest; - break; - } - if($webbie) { - $c = q("select * from channel where channel_address = '%s' limit 1", - dbesc($webbie) - ); - } - if(! $c) { - - // They are authenticating ultimately to the site and not to a particular channel. - // Any channel will do, providing it's currently active. We just need to have an - // identity to attach to the packet we send back. So find one. - $c = q("select * from channel where not ( channel_pageflags & %d ) limit 1", - intval(PAGE_REMOVED) - ); + // They are authenticating ultimately to the site and not to a particular channel. + // Any channel will do, providing it's currently active. We just need to have an + // identity to attach to the packet we send back. So find one. - if(! $c) { + $c = q("select * from channel where not ( channel_pageflags & %d ) limit 1", + intval(PAGE_REMOVED) + ); - // nobody here + if(! $c) { + // nobody here - logger('mod_zot: auth: unable to find channel ' . $webbie); - goaway($desturl); - } + logger('mod_zot: auth: unable to find a response channel'); + goaway($desturl); } // Try and find a hubloc for the person attempting to auth @@ -153,7 +124,7 @@ function post_init(&$a) { goaway($desturl); } - logger('mod_zot: auth request received from ' . $x[0]['xchan_addr'] . ' for ' . (($webbie) ? $webbie : 'undefined')); + logger('mod_zot: auth request received from ' . $x[0]['xchan_addr'] ); // check credentials and access @@ -166,10 +137,12 @@ function post_init(&$a) { $already_authed = ((($remote) && ($x[0]['hubloc_hash'] == $remote)) ? true : false); if(! $already_authed) { - // Auth packets MUST use ultra top-secret hush-hush mode - $p = zot_build_packet($c[0],$type = 'auth_check', - array(array('guid' => $x[0]['hubloc_guid'],'guid_sig' => $x[0]['hubloc_guid_sig'])), - $x[0]['hubloc_sitekey'], $sec); + + // Auth packets MUST use ultra top-secret hush-hush mode - e.g. the entire packet is encrypted using the site private key + // The actual channel sending the packet ($c[0]) is not important, but this provides a generic zot packet with a sender + // which can be verified + + $p = zot_build_packet($c[0],$type = 'auth_check', array(array('guid' => $x[0]['hubloc_guid'],'guid_sig' => $x[0]['hubloc_guid_sig'])), $x[0]['hubloc_sitekey'], $sec); $result = zot_zot($x[0]['hubloc_callback'],$p); if(! $result['success']) { logger('mod_zot: auth_check callback failed.'); @@ -211,7 +184,7 @@ function post_init(&$a) { logger('mod_zot: auth success from ' . $x[0]['xchan_addr'] . ' for ' . $webbie); } else { - logger('mod_zot: still not authenticated: ' . $x[0]['xchan_addr']); + logger('mod_zot: magic-auth failure - not authenticated: ' . $x[0]['xchan_addr']); q("update hubloc set hubloc_status = (hubloc_status | %d ) where hubloc_addr = '%s'", intval(HUBLOC_RECEIVE_ERROR), dbesc($x[0]['xchan_addr']) @@ -391,6 +364,7 @@ function post_post(&$a) { logger('mod_zot: ' . print_r($_REQUEST,true), LOGGER_DEBUG); + $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'],true); @@ -403,17 +377,10 @@ function post_post(&$a) { */ if(array_key_exists('iv',$data)) { + $encrypted_packet = true; $data = crypto_unencapsulate($data,get_config('system','prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); - -// susceptible to Bleichenbacher's attack -// if(! $data) { -// $ret['message'] = 'Decryption failed.'; -// json_return_and_die($ret); -// } - $data = json_decode($data,true); - } if(! $data) { @@ -552,6 +519,8 @@ function post_post(&$a) { } + + /** * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. @@ -606,6 +575,91 @@ function post_post(&$a) { if(array_key_exists('recipients',$data)) $recipients = $data['recipients']; + + if($msgtype === 'auth_check') { + + /** + * Requestor visits /magic/?dest=somewhere on their own site with a browser + * magic redirects them to $destsite/post [with auth args....] + * $destsite sends an auth_check packet to originator site + * The auth_check packet is handled here by the originator's site + * - the browser session is still waiting + * inside $destsite/post for everything to verify + * If everything checks out we'll return a token to $destsite + * and then $destsite will verify the token, authenticate the browser + * session and then redirect to the original destination. + * If authentication fails, the redirection to the original destination + * will still take place but without authentication. + */ + logger('mod_zot: auth_check', LOGGER_DEBUG); + + if(! $encrypted_packet) { + logger('mod_zot: auth_check packet was not encrypted.'); + json_return_and_die($ret); + } + + $arr = $data['sender']; + $sender_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); + + // garbage collect any old unused notifications + q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); + + $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", + dbesc($sender_hash) + ); + + // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in + // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. + // First verify their signature. We will have obtained a zot-info packet from them as part of the sender + // verification. + + if((! $y) || (! rsa_verify($data['secret'],base64url_decode($data['secret_sig']),$y[0]['xchan_pubkey']))) { + logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); + json_return_and_die($ret); + } + + // There should be exactly one recipient, the original auth requestor + + if($data['recipients']) { + + $arr = $data['recipients'][0]; + $recip_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); + $c = q("select channel_id, channel_prvkey from channel where channel_hash = '%s' limit 1", + dbesc($recip_hash) + ); + if(! $c) { + logger('mod_zot: auth_check: recipient channel not found.'); + json_return_and_die($ret); + } + + $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash,$c[0]['channel_prvkey'])); + + // This additionally checks for forged sites since we already stored the expected result in meta + // and we've already verified that this is them via zot_gethub() and that their key signed our token + + $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", + intval($c[0]['channel_id']), + dbesc($data['secret']), + dbesc($data['sender']['url']) + ); + if(! $z) { + logger('mod_zot: auth_check: verification key not found.'); + json_return_and_die($ret); + } + $r = q("delete from verify where id = %d limit 1", + intval($z[0]['id']) + ); + + logger('mod_zot: auth_check: success', LOGGER_DEBUG); + $ret['success'] = true; + $ret['confirm'] = $confirm; + json_return_and_die($ret); + + } + json_return_and_die($ret); + } + + if($msgtype === 'purge') { if($recipients) { // basically this means "unfriend" @@ -696,66 +750,6 @@ function post_post(&$a) { } - if($msgtype === 'auth_check') { - logger('mod_zot: auth_check'); - $arr = $data['sender']; - $sender_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); - - // garbage collect any old unused notifications - q("delete from verify where type = 'auth' and created < UTC_TIMESTAMP() - INTERVAL 10 MINUTE"); - - $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", - dbesc($sender_hash) - ); - // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in - // the verify table. It is now coming back to us as 'secret' and is signed by the other site. - // First verify their signature. - - if((! $y) || (! rsa_verify($data['secret'],base64url_decode($data['secret_sig']),$y[0]['xchan_pubkey']))) { - logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); - json_return_and_die($ret); - } - - // There should be exactly one recipient - if($data['recipients']) { - - $arr = $data['recipients'][0]; - $recip_hash = base64url_encode(hash('whirlpool',$arr['guid'] . $arr['guid_sig'], true)); - $c = q("select channel_id, channel_prvkey from channel where channel_hash = '%s' limit 1", - dbesc($recip_hash) - ); - if(! $c) { - logger('mod_zot: auth_check: recipient channel not found.'); - json_return_and_die($ret); - } - - $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash,$c[0]['channel_prvkey'])); - - // This additionally checks for forged senders since we already stored the expected result in meta - // and we've already verified that this is them via zot_gethub() and that their key signed our token - - $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", - intval($c[0]['channel_id']), - dbesc($data['secret']), - dbesc($sender_hash) - ); - if(! $z) { - logger('mod_zot: auth_check: verification key not found.'); - json_return_and_die($ret); - } - $r = q("delete from verify where id = %d limit 1", - intval($z[0]['id']) - ); - - logger('mod_zot: auth_check: success', LOGGER_DEBUG); - $ret['success'] = true; - $ret['confirm'] = $confirm; - json_return_and_die($ret); - - } - json_return_and_die($ret); - } - // catchall json_return_and_die($ret); diff --git a/version.inc b/version.inc index e64750193..fb4bb2407 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -2013-12-01.514 +2013-12-02.515 diff --git a/view/tpl/group_side.tpl b/view/tpl/group_side.tpl index ebc820530..d2d8a77e2 100755 --- a/view/tpl/group_side.tpl +++ b/view/tpl/group_side.tpl @@ -23,11 +23,6 @@ <div id="sidebar-new-group"> <a href="group/new">{{$createtext}}</a> </div> - {{if $ungrouped}} - <div id="sidebar-ungrouped"> - <a href="nogroup">{{$ungrouped}}</a> - </div> - {{/if}} </div> |