From a4d733b68dad7f70857309b7da8e95cb164da746 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 22 Jan 2010 15:04:53 -0800 Subject: [PATCH 001/130] Fix for PoweredByStatusNetPlugin to be localizable (was broken for non-English word order) (Note the .po files will have to be added manually for now as we haven't set TranslateWiki up for plugins I think) --- .../PoweredByStatusNetPlugin.php | 5 +-- .../locale/PoweredByStatusNet.po | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po diff --git a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php index c59fcca890..14d1608d3c 100644 --- a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php +++ b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php @@ -46,8 +46,9 @@ class PoweredByStatusNetPlugin extends Plugin function onEndAddressData($action) { $action->elementStart('span', 'poweredby'); - $action->text(_('powered by')); - $action->element('a', array('href' => 'http://status.net/'), 'StatusNet'); + $action->raw(sprintf(_m('powered by %s'), + sprintf('%s', + _m('StatusNet')))); $action->elementEnd('span'); return true; diff --git a/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po new file mode 100644 index 0000000000..bd39124efe --- /dev/null +++ b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po @@ -0,0 +1,32 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-22 15:03-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: PoweredByStatusNetPlugin.php:49 +#, php-format +msgid "powered by %s" +msgstr "" + +#: PoweredByStatusNetPlugin.php:51 +msgid "StatusNet" +msgstr "" + +#: PoweredByStatusNetPlugin.php:64 +msgid "" +"Outputs powered by StatusNet after site " +"name." +msgstr "" From 51fdba09d4bdc2b933e40c9ea5ea6de76c4aa799 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 16:49:49 +0100 Subject: [PATCH 002/130] Event hooks for before and after site_notice --- EVENTS.txt | 6 ++++++ lib/action.php | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/EVENTS.txt b/EVENTS.txt index 6e6afa070f..1ed670697b 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -150,6 +150,12 @@ StartAddressData: Allows the site owner to provide additional information about EndAddressData: At the end of
- $action: the current action +StartShowSiteNotice: Before showing site notice +- $action: the current action + +EndShowSiteNotice: After showing site notice +- $action: the current action + StartLoginGroupNav: Before showing the login and register navigation menu - $action: the current action diff --git a/lib/action.php b/lib/action.php index 171bea17c7..22425e6821 100644 --- a/lib/action.php +++ b/lib/action.php @@ -373,7 +373,11 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('div', array('id' => 'header')); $this->showLogo(); $this->showPrimaryNav(); - $this->showSiteNotice(); + if (Event::handle('StartShowSiteNotice', array($this))) { + $this->showSiteNotice(); + + Event::handle('EndShowSiteNotice', array($this)); + } if (common_logged_in()) { $this->showNoticeForm(); } else { From aaa2dd2712504dc9399d6cc5e1a002eb08ce6d4f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:02:55 +0000 Subject: [PATCH 003/130] Styles for application details page --- theme/base/css/display.css | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 2e4c88dfa3..5e280bdfee 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -894,6 +894,39 @@ font-weight:normal; margin-right:11px; } +#showapplication .entity_profile { +width:68%; +} +#showapplication .entity_profile img { +max-width:96px; +max-height:96px; +} +#showapplication .entity_profile .entity_fn { +margin-left:0; +} +#showapplication .entity_profile .entity_fn .fn:before, +#showapplication .entity_profile .entity_fn .fn:after { +content:''; +} +#showapplication .entity_data { +clear:both; +margin-bottom:18px; +} +#showapplication .entity_data h2 { +display:none; +} +#showapplication .entity_data dl { +margin-bottom:18px; +} +#showapplication .entity_data dt { +font-weight:bold; +} +#showapplication .entity_data dd { +margin-left:1.795%; +font-family:monospace; +font-size:1.3em; +} + /* NOTICE */ .notice, .profile { From f3c732587ca17d7fe693d2f3830bd65f3b1f678f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:04:20 +0000 Subject: [PATCH 004/130] Styles for application list --- theme/base/css/display.css | 16 +++++++++++++++- theme/default/css/display.css | 2 ++ theme/identica/css/display.css | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 5e280bdfee..c33f64aca0 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -894,6 +894,19 @@ font-weight:normal; margin-right:11px; } +/*applications*/ +.applications { +margin-bottom:18px; +float:left; +width:100%; +} +.applications li { +list-style-type:none; +} +.application img { +max-width:96px; +max-height:96px; +} #showapplication .entity_profile { width:68%; } @@ -929,7 +942,8 @@ font-size:1.3em; /* NOTICE */ .notice, -.profile { +.profile, +.application { position:relative; padding-top:11px; padding-bottom:11px; diff --git a/theme/default/css/display.css b/theme/default/css/display.css index 8a2c011752..4a45303caa 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -129,6 +129,7 @@ color:#002FA7; .notice, .profile, +.application, #content tbody tr { border-top-color:#C8D1D5; } @@ -378,6 +379,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } #content .notices li:hover, +#content .applications li:hover, #content tbody tr:hover { background-color:rgba(240, 240, 240, 0.2); } diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 4ee48459d0..96e3c61bc6 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -129,6 +129,7 @@ color:#002FA7; .notice, .profile, +.application, #content tbody tr { border-top-color:#CEE1E9; } @@ -377,6 +378,7 @@ box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); -webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3); } #content .notices li:hover, +#content .applications li:hover, #content tbody tr:hover { background-color:rgba(240, 240, 240, 0.2); } From cae113941d9c2aadcbe4763a481b2bb25a205075 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:33:36 +0000 Subject: [PATCH 005/130] Added key icon for application key and secret rest action --- theme/base/images/icons/icons-01.gif | Bin 3607 -> 3650 bytes theme/base/images/icons/twotone/green/key.gif | Bin 0 -> 76 bytes theme/default/css/display.css | 6 +++++- theme/identica/css/display.css | 6 +++++- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 theme/base/images/icons/twotone/green/key.gif diff --git a/theme/base/images/icons/icons-01.gif b/theme/base/images/icons/icons-01.gif index 06202a047b40daed0f0ceec269d9025232de4dc6..f93d33d79bce50e1e407c98d534c6cbff24d4366 100644 GIT binary patch delta 96 zcmbO(b4Z5A-P6s&GEs`*6#Mi48+raQ3IG59U-2gkBNxMe1|0?<07`9^W&XltIDMjl zq}F7kpC!_klVmOXyvp-$e2>3vRuHD%YdbahO2y=z?nN${lO9D(aobtK$jD#~0Koes A;Q#;t delta 53 zcmX>kGhK$q-P6s&GEs`5gZ+8HMxK96!tqlC6o0ZXaxwg8&|v@qpwwnr<}X}q-}xCC G8LR=HZ4L Date: Tue, 12 Jan 2010 02:51:33 +0000 Subject: [PATCH 006/130] Styles for image max width/height and radio form controls --- theme/base/css/display.css | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index c33f64aca0..507733979f 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -903,17 +903,15 @@ width:100%; .applications li { list-style-type:none; } -.application img { +.application img, +#showapplication .entity_profile img, +#editapplication .form_data #application_icon img { max-width:96px; max-height:96px; } #showapplication .entity_profile { width:68%; } -#showapplication .entity_profile img { -max-width:96px; -max-height:96px; -} #showapplication .entity_profile .entity_fn { margin-left:0; } @@ -939,6 +937,10 @@ margin-left:1.795%; font-family:monospace; font-size:1.3em; } +#editapplication .form_data #application_types label.radio, +#editapplication .form_data #default_access_types label.radio { +width:15%; +} /* NOTICE */ .notice, From fa218ec65dde91f129eb17c7d443bec4017d170c Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 13 Jan 2010 22:05:22 -0500 Subject: [PATCH 007/130] Revert "Drop the Google Client API-based AJAX geolocation lookup shim -- it fails to ask for user permission, causing us quite a bit of difficulty." This reverts commit 749b8b5b8ca4d1c39d350879aadddbdb9d8b71d5. --- js/geometa.js | 123 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/js/geometa.js b/js/geometa.js index 87e3c99a16..21deb18852 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -1,4 +1,4 @@ -// A shim to implement the W3C Geolocation API Specification using Gears +// A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){ // -- BEGIN GEARS_INIT @@ -96,9 +96,122 @@ var GearsGeoLocation = (function() { }; }); -// If you have Gears installed use that -if (window.google && google.gears) { - navigator.geolocation = GearsGeoLocation(); -} +var AjaxGeoLocation = (function() { + // -- PRIVATE + var loading = false; + var loadGoogleLoader = function() { + if (!hasGoogleLoader() && !loading) { + loading = true; + var s = document.createElement('script'); + s.src = (document.location.protocol == "https:"?"https://":"http://") + 'www.google.com/jsapi?callback=_google_loader_apiLoaded'; + s.type = "text/javascript"; + document.getElementsByTagName('body')[0].appendChild(s); + } + }; + + var queue = []; + var addLocationQueue = function(callback) { + queue.push(callback); + } + + var runLocationQueue = function() { + if (hasGoogleLoader()) { + while (queue.length > 0) { + var call = queue.pop(); + call(); + } + } + } + + window['_google_loader_apiLoaded'] = function() { + runLocationQueue(); + } + + var hasGoogleLoader = function() { + return (window['google'] && google['loader']); + } + + var checkGoogleLoader = function(callback) { + if (hasGoogleLoader()) return true; + + addLocationQueue(callback); + + loadGoogleLoader(); + + return false; + }; + + loadGoogleLoader(); // start to load as soon as possible just in case + + // -- PUBLIC + return { + shim: true, + + type: "ClientLocation", + + lastPosition: null, + + getCurrentPosition: function(successCallback, errorCallback, options) { + var self = this; + if (!checkGoogleLoader(function() { + self.getCurrentPosition(successCallback, errorCallback, options); + })) return; + + if (google.loader.ClientLocation) { + var cl = google.loader.ClientLocation; + + var position = { + coords: { + latitude: cl.latitude, + longitude: cl.longitude, + altitude: null, + accuracy: 43000, // same as Gears accuracy over wifi? + altitudeAccuracy: null, + heading: null, + speed: null, + }, + // extra info that is outside of the bounds of the core API + address: { + city: cl.address.city, + country: cl.address.country, + country_code: cl.address.country_code, + region: cl.address.region + }, + timestamp: new Date() + }; + + successCallback(position); + + this.lastPosition = position; + } else if (errorCallback === "function") { + errorCallback({ code: 3, message: "Using the Google ClientLocation API and it is not able to calculate a location."}); + } + }, + + watchPosition: function(successCallback, errorCallback, options) { + this.getCurrentPosition(successCallback, errorCallback, options); + + var self = this; + var watchId = setInterval(function() { + self.getCurrentPosition(successCallback, errorCallback, options); + }, 10000); + + return watchId; + }, + + clearWatch: function(watchId) { + clearInterval(watchId); + }, + + getPermission: function(siteName, imageUrl, extraMessage) { + // for now just say yes :) + return true; + } + + }; +}); + +// If you have Gears installed use that, else use Ajax ClientLocation +navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation(); })(); From 414c80a18d6bbcb75e816a5f4ff10d6c54107d41 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:16:44 +0000 Subject: [PATCH 008/130] Removed extra comma in object --- js/geometa.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/geometa.js b/js/geometa.js index 21deb18852..6bad095ec8 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -168,7 +168,7 @@ var AjaxGeoLocation = (function() { accuracy: 43000, // same as Gears accuracy over wifi? altitudeAccuracy: null, heading: null, - speed: null, + speed: null }, // extra info that is outside of the bounds of the core API address: { From ab53a8704b13a20f8ae885d2b92883e0c7101424 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:42:32 +0000 Subject: [PATCH 009/130] Some JSlint-ing --- js/geometa.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/js/geometa.js b/js/geometa.js index 6bad095ec8..bba59b4486 100644 --- a/js/geometa.js +++ b/js/geometa.js @@ -1,5 +1,5 @@ // A shim to implement the W3C Geolocation API Specification using Gears or the Ajax API -if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) (function(){ +if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) { (function(){ // -- BEGIN GEARS_INIT (function() { @@ -23,8 +23,7 @@ if (typeof navigator.geolocation == "undefined" || navigator.geolocation.shim ) } } catch (e) { // Safari - if ((typeof navigator.mimeTypes != 'undefined') - && navigator.mimeTypes["application/x-googlegears"]) { + if ((typeof navigator.mimeTypes != 'undefined') && navigator.mimeTypes["application/x-googlegears"]) { factory = document.createElement("object"); factory.style.display = "none"; factory.width = 0; @@ -64,8 +63,8 @@ var GearsGeoLocation = (function() { return function(position) { callback(position); self.lastPosition = position; - } - } + }; + }; // -- PUBLIC return { @@ -112,7 +111,7 @@ var AjaxGeoLocation = (function() { var queue = []; var addLocationQueue = function(callback) { queue.push(callback); - } + }; var runLocationQueue = function() { if (hasGoogleLoader()) { @@ -121,18 +120,18 @@ var AjaxGeoLocation = (function() { call(); } } - } + }; window['_google_loader_apiLoaded'] = function() { runLocationQueue(); - } + }; var hasGoogleLoader = function() { return (window['google'] && google['loader']); - } + }; var checkGoogleLoader = function(callback) { - if (hasGoogleLoader()) return true; + if (hasGoogleLoader()) { return true; } addLocationQueue(callback); @@ -155,7 +154,7 @@ var AjaxGeoLocation = (function() { var self = this; if (!checkGoogleLoader(function() { self.getCurrentPosition(successCallback, errorCallback, options); - })) return; + })) { return; } if (google.loader.ClientLocation) { var cl = google.loader.ClientLocation; @@ -215,3 +214,4 @@ var AjaxGeoLocation = (function() { navigator.geolocation = (window.google && google.gears) ? GearsGeoLocation() : AjaxGeoLocation(); })(); +} From 68c864c95a10b9018862c8ff3677c1185f1df496 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 19:44:37 +0000 Subject: [PATCH 010/130] JSLinting on JSON --- js/util.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/js/util.js b/js/util.js index a749c5d9f4..2d19b8df25 100644 --- a/js/util.js +++ b/js/util.js @@ -529,13 +529,13 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeDataGeo).attr('checked', true); var cookieValue = { - 'NLat': data.lat, - 'NLon': data.lon, - 'NLNS': lns, - 'NLID': lid, - 'NLN': NLN_text, - 'NLNU': location.url, - 'NDG': true + NLat: data.lat, + NLon: data.lon, + NLNS: lns, + NLID: lid, + NLN: NLN_text, + NLNU: location.url, + NDG: true }; $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); }); @@ -570,9 +570,9 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLon).val(position.coords.longitude); var data = { - 'lat': position.coords.latitude, - 'lon': position.coords.longitude, - 'token': $('#token').val() + lat: position.coords.latitude, + lon: position.coords.longitude, + token: $('#token').val() }; getJSONgeocodeURL(geocodeURL, data); From f043bc62a53250eaf383e9f36f95031fcd4e1160 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 20:10:46 +0000 Subject: [PATCH 011/130] Added missing position paramater --- js/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index 2d19b8df25..0947319554 100644 --- a/js/util.js +++ b/js/util.js @@ -498,7 +498,7 @@ var SN = { // StatusNet $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); } - function getJSONgeocodeURL(geocodeURL, data) { + function getJSONgeocodeURL(geocodeURL, data, position) { $.getJSON(geocodeURL, data, function(location) { var lns, lid; @@ -575,7 +575,7 @@ var SN = { // StatusNet token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data); + getJSONgeocodeURL(geocodeURL, data, position); }, function(error) { @@ -602,7 +602,7 @@ var SN = { // StatusNet 'token': $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data); + getJSONgeocodeURL(geocodeURL, data, position); } else { removeNoticeDataGeo(); From 2a1ab137cc233314b4c115d0687c27c7dbaec96f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sat, 16 Jan 2010 20:57:18 +0000 Subject: [PATCH 012/130] Using visibility:hidden instead of display:none for checkbox --- theme/base/css/display.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 507733979f..fc11a9234d 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -73,7 +73,7 @@ input.checkbox, input.radio { position:relative; top:2px; -left:0; +left:auto; border:0; } @@ -567,7 +567,8 @@ float:right; font-size:0.8em; } -.form_notice #notice_data-geo_wrap label { +.form_notice #notice_data-geo_wrap label, +.form_notice #notice_data-geo_wrap input { position:absolute; top:25px; right:4px; @@ -578,7 +579,7 @@ height:16px; display:block; } .form_notice #notice_data-geo_wrap input { -display:none; +visibility:hidden; } .form_notice #notice_data-geo_wrap label { font-weight:normal; From 37e642bece04a208a7c7b0bffe8bb1dc3f976d1e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 14:04:47 +0000 Subject: [PATCH 013/130] Inline script for maxlength is deprecated --- plugins/MobileProfile/MobileProfilePlugin.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 14d2500e8f..d426fc282b 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -356,8 +356,6 @@ class MobileProfilePlugin extends WAP20Plugin $contentLimit = Notice::maxContent(); - $form->out->inlineScript('maxLength = ' . $contentLimit . ';'); - if ($contentLimit > 0) { $form->out->element('div', array('id' => 'notice_text-count'), $contentLimit); From 2742494fe82d5c12016d46e2de5a849efe2ebee6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 19:45:35 +0000 Subject: [PATCH 014/130] Updated UI for notice aside content and notice options in MobileProfile --- plugins/MobileProfile/mp-screen.css | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 3eefc0c8e0..472fbb0013 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -179,10 +179,12 @@ padding-bottom:4px; } .notice div.entry-content { margin-left:0; -width:62.5%; +width:75%; +max-width:100%; +min-width:0; } .notice-options { -width:34%; +width:50px; margin-right:1%; } @@ -190,12 +192,29 @@ margin-right:1%; width:16px; height:16px; } -.notice-options a, -.notice-options input { +.notice .notice-options a, +.notice .notice-options input { box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; } +.notice .notice-options a, +.notice .notice-options form { +margin:-4px 0 0 0; +} +.notice .notice-options .notice_repeat, +.notice .notice-options .notice_delete { +margin-top:18px; +} +.notice .notice-options .notice_reply, +.notice .notice-options .notice_repeat { +margin-left:18px; +} + + +.notice .notice-options .notice_delete { +float:left; +} .entity_profile { width:auto; From 940fc463c814ac26cdec9e3afdf67fd9187519a3 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 17 Jan 2010 22:31:47 +0000 Subject: [PATCH 015/130] Took out focus out of textare when location share is enabled/disabled. Also avoids the conflict with the URL fragment on the conversation page. --- js/util.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/util.js b/js/util.js index 0947319554..d93467d4a5 100644 --- a/js/util.js +++ b/js/util.js @@ -628,8 +628,6 @@ var SN = { // StatusNet else { removeNoticeDataGeo(); } - - $('#'+SN.C.S.NoticeDataText).focus(); }).change(); } }, From f92a9dad8c68c8a1355a2f00b29d21209b62dd34 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 11:12:05 +0000 Subject: [PATCH 016/130] Moved farbtastic's stylesheet to use relative paths for its own images --- {theme/base/css => js/farbtastic}/farbtastic.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename {theme/base/css => js/farbtastic}/farbtastic.css (70%) diff --git a/theme/base/css/farbtastic.css b/js/farbtastic/farbtastic.css similarity index 70% rename from theme/base/css/farbtastic.css rename to js/farbtastic/farbtastic.css index 7efcc73c3b..a88e7b868b 100644 --- a/theme/base/css/farbtastic.css +++ b/js/farbtastic/farbtastic.css @@ -16,17 +16,17 @@ height: 101px; } .farbtastic .wheel { - background: url(../../../js/farbtastic/wheel.png) no-repeat; + background: url(wheel.png) no-repeat; width: 195px; height: 195px; } .farbtastic .overlay { - background: url(../../../js/farbtastic/mask.png) no-repeat; + background: url(mask.png) no-repeat; } .farbtastic .marker { width: 17px; height: 17px; margin: -8px 0 0 -8px; overflow: hidden; - background: url(../../../js/farbtastic/marker.png) no-repeat; + background: url(marker.png) no-repeat; } From 0f3658d3da788b071da408839195526a10da5e2e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 11:29:05 +0000 Subject: [PATCH 017/130] Updated path to farbtastic stylesheet --- actions/designadminpanel.php | 2 +- lib/designsettings.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/designadminpanel.php b/actions/designadminpanel.php index f862aff0eb..72ad6ade2a 100644 --- a/actions/designadminpanel.php +++ b/actions/designadminpanel.php @@ -289,7 +289,7 @@ class DesignadminpanelAction extends AdminPanelAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('css/farbtastic.css','base','screen, projection, tv'); + $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv'); } /** diff --git a/lib/designsettings.php b/lib/designsettings.php index b70ba0dfca..8e44c03a92 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -314,7 +314,7 @@ class DesignSettingsAction extends AccountSettingsAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('css/farbtastic.css','base','screen, projection, tv'); + $this->cssLink('js/farbtastic/farbtastic.css',null,'screen, projection, tv'); } /** From c367e09d1130d4d4f01e029c4659b18f55ae6d8f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 12:55:14 +0000 Subject: [PATCH 018/130] Some JS cleaning up for NoticeLocationAttach (which fixes also fixes a few bugs in WebKit) --- js/util.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/util.js b/js/util.js index d93467d4a5..a7339010a7 100644 --- a/js/util.js +++ b/js/util.js @@ -498,7 +498,7 @@ var SN = { // StatusNet $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); } - function getJSONgeocodeURL(geocodeURL, data, position) { + function getJSONgeocodeURL(geocodeURL, data) { $.getJSON(geocodeURL, data, function(location) { var lns, lid; @@ -513,7 +513,7 @@ var SN = { // StatusNet } if (typeof(location.name) == 'undefined') { - NLN_text = position.coords.latitude + ';' + position.coords.longitude; + NLN_text = data.lat + ';' + data.lon; } else { NLN_text = location.name; @@ -575,7 +575,7 @@ var SN = { // StatusNet token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data, position); + getJSONgeocodeURL(geocodeURL, data); }, function(error) { @@ -597,12 +597,12 @@ var SN = { // StatusNet else { if (NLat.length > 0 && NLon.length > 0) { var data = { - 'lat': NLat, - 'lon': NLon, - 'token': $('#token').val() + lat: NLat, + lon: NLon, + token: $('#token').val() }; - getJSONgeocodeURL(geocodeURL, data, position); + getJSONgeocodeURL(geocodeURL, data); } else { removeNoticeDataGeo(); From c3ee1af7bee3b7268fc4cfd40ef45d9ca512fe97 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 18 Jan 2010 17:17:02 +0000 Subject: [PATCH 019/130] Missing null className for incoming email form legend --- actions/emailsettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index bfef2970da..08608348cd 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -130,7 +130,7 @@ class EmailsettingsAction extends AccountSettingsAction if (common_config('emailpost', 'enabled') && $user->email) { $this->elementStart('fieldset', array('id' => 'settings_email_incoming')); - $this->element('legend',_('Incoming email')); + $this->element('legend', null, _('Incoming email')); if ($user->incomingemail) { $this->elementStart('p'); $this->element('span', 'address', $user->incomingemail); From e3ee5663abfc5fa873f4b2c76f6f89971c3c9aae Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 20 Jan 2010 18:32:24 +0100 Subject: [PATCH 020/130] Updated notice item view where a) notice text no longer wraps around (under author's photo) b) supplemental notice content and options will start right under notice text. --- plugins/MobileProfile/mp-screen.css | 12 +++++++++++- theme/base/css/display.css | 22 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 472fbb0013..76071352d0 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -176,8 +176,18 @@ margin-bottom:0; .profile { padding-top:4px; padding-bottom:4px; +min-height:65px; } -.notice div.entry-content { +#content .notice .entry-title { +float:left; +width:100%; +margin-left:0; +} +#content .notice .author .photo { +position:static; +float:left; +} +#content .notice div.entry-content { margin-left:0; width:75%; max-width:100%; diff --git a/theme/base/css/display.css b/theme/base/css/display.css index fc11a9234d..82670c964a 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1006,6 +1006,16 @@ float:left; #shownotice .vcard .photo { margin-bottom:4px; } +#content .notice .author .photo { +position:absolute; +top:11px; +left:0; +float:none; +} +#content .notice .entry-title { +margin-left:59px; +} + .vcard .url { text-decoration:none; } @@ -1014,13 +1024,19 @@ text-decoration:underline; } .notice .entry-title { -float:left; -width:100%; overflow:hidden; } .notice .entry-title.ov { overflow:visible; } +#showstream .notice .entry-title, +#showstream .notice div.entry-content { +margin-left:0; +} +#shownotice .notice .entry-title, +#shownotice .notice div.entry-content { +margin-left:110px; +} #shownotice .notice .entry-title { font-size:2.2em; } @@ -1050,7 +1066,6 @@ max-width:70%; } #showstream .notice div.entry-content, #shownotice .notice div.entry-content { -margin-left:0; max-width:79%; } @@ -1114,6 +1129,7 @@ position:relative; font-size:0.95em; width:113px; float:right; +margin-top:3px; margin-right:4px; } From 51775e38bab8f3c41aa19bed84a38d1a7f534e37 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 20 Jan 2010 18:50:48 +0100 Subject: [PATCH 021/130] Better alignment for notice options in MobileProfile --- plugins/MobileProfile/mp-screen.css | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/plugins/MobileProfile/mp-screen.css b/plugins/MobileProfile/mp-screen.css index 76071352d0..04fa5fb002 100644 --- a/plugins/MobileProfile/mp-screen.css +++ b/plugins/MobileProfile/mp-screen.css @@ -194,7 +194,7 @@ max-width:100%; min-width:0; } .notice-options { -width:50px; +width:43px; margin-right:1%; } @@ -202,6 +202,13 @@ margin-right:1%; width:16px; height:16px; } +.notice-options form.processing { +background-image:none; +} +#wrap .notice-options form.processing input.submit { +background-position:0 47%; +} + .notice .notice-options a, .notice .notice-options input { box-shadow:none; @@ -212,16 +219,16 @@ box-shadow:none; .notice .notice-options form { margin:-4px 0 0 0; } -.notice .notice-options .notice_repeat, +.notice .notice-options .form_repeat, .notice .notice-options .notice_delete { -margin-top:18px; +margin-top:11px; } -.notice .notice-options .notice_reply, -.notice .notice-options .notice_repeat { -margin-left:18px; +.notice .notice-options .form_favor, +.notice .notice-options .form_disfavor, +.notice .notice-options .form_repeat { +margin-right:11px; } - .notice .notice-options .notice_delete { float:left; } From fed111f08aeba6be5bfc5e924975048e6a8f6521 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 13:23:04 +0100 Subject: [PATCH 022/130] Removed mobile stylesheet from core output. If Mobile support is seeked, MobileProfile plugin should be used. --- lib/action.php | 4 - theme/base/css/mobile.css | 150 -------------------------------------- 2 files changed, 154 deletions(-) delete mode 100644 theme/base/css/mobile.css diff --git a/lib/action.php b/lib/action.php index 22425e6821..e242775585 100644 --- a/lib/action.php +++ b/lib/action.php @@ -199,10 +199,6 @@ class Action extends HTMLOutputter // lawsuit if (Event::handle('StartShowStatusNetStyles', array($this)) && Event::handle('StartShowLaconicaStyles', array($this))) { $this->cssLink('css/display.css',null,'screen, projection, tv'); - if (common_config('site', 'mobile')) { - // TODO: "handheld" CSS for other mobile devices - $this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit - } $this->cssLink('css/print.css','base','print'); Event::handle('EndShowStatusNetStyles', array($this)); Event::handle('EndShowLaconicaStyles', array($this)); diff --git a/theme/base/css/mobile.css b/theme/base/css/mobile.css deleted file mode 100644 index f6c53ea8dc..0000000000 --- a/theme/base/css/mobile.css +++ /dev/null @@ -1,150 +0,0 @@ -/** theme: base - * - * @package StatusNet - * @author Meitar Moscovitz - * @author Sarven Capadisli - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -body { -font-size:2.5em; -} - -#wrap { -width:95%; -} - -#header, -#header address, -#anon_notice, -#site_nav_local_views .nav, -#form_notice, -#form_notice .form_data li, -#core, -#content_inner, -#notices_primary, -.notice, -.notice .entry-title, -.notice div.entry-content, -.notice-options, -.notice .notice-options a, -.pagination, -.pagination .nav, -.aside .section { -float:none; -} - -.notice-options .notice_reply, -.notice-options .notice_delete, -.notice-options .form_favor, -.notice-options .form_disfavor { -position:static; -} - -#form_notice, -#anon_notice, -#footer, -#form_notice .form_actions input.submit { -width:auto; -} - -.form_settings label { -width:25%; -} -.form_settings .form_data p.form_guide { -margin-left:26%; -} - -#site_nav_global_primary { -width:75%; -} - -.entity_profile { -width:65%; -} -.entity_actions { -margin-left:0; -} - -#form_notice, -#anon_notice { -clear:both; -} - -#content, -#aside_primary { -width:96%; -padding-left:2%; -padding-right:2%; -} - -#site_notice { -position:static; -float:right; -clear:right; -width:75%; -margin-right:0; -margin-bottom:11px; -} - -.notices { -font-size:1.5em; -} - -#form_notice textarea { -width:80%; -height:5em; -} -#form_notice .form_note { -right:20%; -top:6em; -} - - -.vcard .photo, -.section .vcard .photo { -margin-right:18px; -} -.notice, -.profile { -margin-bottom:18px; -} - -.notices .entry-title, -.notices div.entry-content { -width:90%; -} -.notice div.entry-content { -margin-left:0; -} - -.notice .author .photo { -height:4.5em; -width:4.5em; -} -.notice-options { -position:absolute; -top:0; -right:0; -padding-left:7%; -width:3%; -} - -.notice-options .notice_delete a { -float:left; -} -.pagination .nav { -overflow:auto; -} - -#export_data { -display:none; -} - -#site_nav_local_views li { -margin-right:4px; -} -#site_nav_local_views a { -padding:18px 11px; -} From 38fe4ad9586706c69b46d68612d5a20415433ae7 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Sun, 24 Jan 2010 15:34:40 +0100 Subject: [PATCH 023/130] Added version info for MobileProfile plugin --- plugins/MobileProfile/MobileProfilePlugin.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index d426fc282b..5c913836dc 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -414,7 +414,15 @@ class MobileProfilePlugin extends WAP20Plugin return $proto.'://'.$serverpart.'/'.$pathpart.$relative; } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'MobileProfile', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:MobileProfile', + 'rawdescription' => + _m('XHTML MobileProfile output for supporting user agents.')); + return true; + } } - - -?> From 238998eca017a3ddd6cc0c01a75f8ace37661b5d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 18:18:24 -0500 Subject: [PATCH 024/130] save nickname and wildcard when setting up status network --- classes/Status_network.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/classes/Status_network.php b/classes/Status_network.php index ef8e1ed431..445f8a5a3c 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -48,6 +48,7 @@ class Status_network extends DB_DataObject static $cache = null; static $base = null; + static $wildcard = null; /** * @param string $dbhost @@ -187,7 +188,12 @@ class Status_network extends DB_DataObject $config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname"; - $config['site']['name'] = $sn->sitename; + $config['site']['name'] = $sn->sitename; + $config['site']['nickname'] = $sn->nickname; + + self::$wildcard = $wildcard; + + $config['site']['wildcard'] =& self::$wildcard; if (!empty($sn->hostname)) { $config['site']['server'] = $sn->hostname; @@ -230,4 +236,13 @@ class Status_network extends DB_DataObject exit; } + + function getServerName() + { + if (!empty($this->hostname)) { + return $this->hostname; + } else { + return $this->nickname . '.' . self::$wildcard; + } + } } From 7cd666ae928b003a8096289009bfd7290f8f6c73 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 18:19:13 -0500 Subject: [PATCH 025/130] defaults for nickname and wildcard --- lib/default.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/default.php b/lib/default.php index 0cb70de2d2..e3a043de12 100644 --- a/lib/default.php +++ b/lib/default.php @@ -30,6 +30,8 @@ $default = array('site' => array('name' => 'Just another StatusNet microblog', + 'nickname' => 'statusnet', + 'wildcard' => null, 'server' => $_server, 'theme' => 'default', 'path' => $_path, From 60ce2ff3d01f22900d62b3d30ed89bc5e9d55167 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 24 Jan 2010 15:44:09 -0800 Subject: [PATCH 026/130] Use new StatusNetwork->serverName() to get full domain for wildcard config until we rebuild queues to be based on nicknames. Fixes live bug with new *.status.net sites breaking queuedaemon.php --- lib/iomaster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iomaster.php b/lib/iomaster.php index 004e92b3ee..3bf82bc6b4 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -88,7 +88,7 @@ abstract class IoMaster $sn = new Status_network(); $sn->find(); while ($sn->fetch()) { - $hosts[] = $sn->hostname; + $hosts[] = $sn->getServerName(); } return $hosts; } From c0b832d19fc478d67752a6692bf71f178346e14a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 10 Nov 2009 17:10:56 -0800 Subject: [PATCH 027/130] Add new OAuth application tables and DataObjects. Also add a new column for consumer secret to consumer table. --- classes/Consumer.php | 5 +++-- classes/Oauth_application.php | 33 ++++++++++++++++++++++++++++++ classes/Oauth_application_user.php | 24 ++++++++++++++++++++++ db/statusnet.sql | 26 +++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100755 classes/Oauth_application.php create mode 100755 classes/Oauth_application_user.php diff --git a/classes/Consumer.php b/classes/Consumer.php index d5b7b7e33a..d17f183a88 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -11,9 +11,10 @@ class Consumer extends Memcached_DataObject public $__table = 'consumer'; // table name public $consumer_key; // varchar(255) primary_key not_null + public $consumer_secret; // varchar(255) not_null public $seed; // char(32) not_null - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $created; // datetime not_null + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ function staticGet($k,$v=null) diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php new file mode 100755 index 0000000000..6ad2db6dde --- /dev/null +++ b/classes/Oauth_application.php @@ -0,0 +1,33 @@ + Date: Thu, 12 Nov 2009 19:34:13 -0800 Subject: [PATCH 028/130] Changed the OAuth app tables to refer to profiles instead of users. Added an owner column to oauth_application. --- classes/Oauth_application.php | 23 ++++++++++++----------- classes/Oauth_application_user.php | 14 +++++++------- db/statusnet.sql | 9 +++++---- 3 files changed, 24 insertions(+), 22 deletions(-) mode change 100755 => 100644 classes/Oauth_application.php mode change 100755 => 100644 classes/Oauth_application_user.php diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php old mode 100755 new mode 100644 index 6ad2db6dde..e2862bf97f --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -2,32 +2,33 @@ /** * Table Definition for oauth_application */ -require_once 'classes/Memcached_DataObject'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Oauth_application extends Memcached_DataObject +class Oauth_application extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ public $__table = 'oauth_application'; // table name public $id; // int(4) primary_key not_null + public $owner; // int(4) not_null public $consumer_key; // varchar(255) not_null public $name; // varchar(255) not_null - public $description; // varchar(255) + public $description; // varchar(255) public $icon; // varchar(255) not_null - public $source_url; // varchar(255) - public $organization; // varchar(255) - public $homepage; // varchar(255) + public $source_url; // varchar(255) + public $organization; // varchar(255) + public $homepage; // varchar(255) public $callback_url; // varchar(255) not_null - public $type; // tinyint(1) - public $access_type; // tinyint(1) + public $type; // tinyint(1) + public $access_type; // tinyint(1) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } - + function staticGet($k,$v=NULL) { + return Memcached_DataObject::staticGet('Oauth_application',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE } diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php old mode 100755 new mode 100644 index a8922f5e77..9e45ece25f --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -2,23 +2,23 @@ /** * Table Definition for oauth_application_user */ -require_once 'classes/Memcached_DataObject'; +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Oauth_application_user extends Memcached_DataObject +class Oauth_application_user extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ public $__table = 'oauth_application_user'; // table name - public $user_id; // int(4) primary_key not_null + public $profile_id; // int(4) primary_key not_null public $application_id; // int(4) primary_key not_null - public $access_type; // tinyint(1) + public $access_type; // tinyint(1) public $created; // datetime not_null /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } - + function staticGet($k,$v=NULL) { + return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE } diff --git a/db/statusnet.sql b/db/statusnet.sql index be0d140927..03e6115e5f 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -210,6 +210,7 @@ create table nonce ( create table oauth_application ( id integer auto_increment primary key comment 'unique identifier', + owner integer not null comment 'owner of the application' references profile (id), consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), name varchar(255) not null comment 'name of the application', description varchar(255) comment 'description of the application', @@ -219,18 +220,18 @@ create table oauth_application ( homepage varchar(255) comment 'homepage for the organization', callback_url varchar(255) not null comment 'url to redirect to after authentication', type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop', - access_type tinyint default 0 comment 'default access type, 0 = read-write, 1 = read-only', + access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table oauth_application_user ( - user_id integer not null comment 'id of the application user' references user (id), + profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), - access_type tinyint default 0 comment 'access type, 0 = read-write, 1 = read-only', + access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', created datetime not null comment 'date this record was created', - constraint primary key (user_id, application_id) + constraint primary key (profile_id, application_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; /* These are used by JanRain OpenID library */ From ae46bc5fff21120ef867fb8ab59833a8f26adf47 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 12 Nov 2009 19:42:18 -0800 Subject: [PATCH 029/130] Started work on interface for displaying connected OAuth apps --- actions/applicationsettings.php | 135 ++++++++++++++++++++++++++++++++ actions/oauthclients.php | 108 +++++++++++++++++++++++++ classes/Profile.php | 23 ++++++ lib/applicationlist.php | 111 ++++++++++++++++++++++++++ lib/connectsettingsaction.php | 4 +- lib/router.php | 4 +- 6 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 actions/applicationsettings.php create mode 100644 actions/oauthclients.php create mode 100644 lib/applicationlist.php diff --git a/actions/applicationsettings.php b/actions/applicationsettings.php new file mode 100644 index 0000000000..16c571feee --- /dev/null +++ b/actions/applicationsettings.php @@ -0,0 +1,135 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; + +/** + * Show connected OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class ApplicationSettingsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Connected Applications'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('You have allowed the following applications to access you account.'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + $profile = $user->getProfile(); + + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = $profile->getApplications($offset, $limit); + + if ($application) { + $al = new ApplicationList($application, $this->user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, + $this->page, 'applicationsettings', + array('nickname' => $this->user->nickname)); + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not authorized any applications to use your account.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); + } + +} diff --git a/actions/oauthclients.php b/actions/oauthclients.php new file mode 100644 index 0000000000..9a29e158e1 --- /dev/null +++ b/actions/oauthclients.php @@ -0,0 +1,108 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; + +/** + * Show a user's registered OAuth applications + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + +class OauthClientsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Applications using %%site_name%%'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Applications you have registered'); + } + + /** + * Content area of the page + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + + } + + /** + * Handle posts to this form + * + * Based on the button that was pressed, muxes out to other functions + * to do the actual task requested. + * + * All sub-functions reload the form with a message -- success or failure. + * + * @return void + */ + + function handlePost() + { + // CSRF protection + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + } + +} diff --git a/classes/Profile.php b/classes/Profile.php index 25d908dbf9..687215b11b 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -352,6 +352,29 @@ class Profile extends Memcached_DataObject return $profile; } + function getApplications($offset = 0, $limit = null) + { + $qry = + 'SELECT oauth_application_user.* ' . + 'FROM oauth_application_user ' . + 'WHERE profile_id = %d ' . + 'ORDER BY created DESC '; + + if ($offset > 0) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $application = new Oauth_application(); + + $cnt = $application->query(sprintf($qry, $this->id)); + + return $application; + } + function subscriptionCount() { $c = common_memcache(); diff --git a/lib/applicationlist.php b/lib/applicationlist.php new file mode 100644 index 0000000000..fed784bb63 --- /dev/null +++ b/lib/applicationlist.php @@ -0,0 +1,111 @@ +. + * + * @category Public + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +define('APPS_PER_PAGE', 20); + +/** + * Widget to show a list of OAuth applications + * + * @category Public + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApplicationList extends Widget +{ + /** Current application, application query */ + var $application = null; + + /** Owner of this list */ + var $owner = null; + + /** Action object using us. */ + var $action = null; + + function __construct($application, $owner=null, $action=null) + { + parent::__construct($action); + + $this->application = $application; + $this->owner = $owner; + $this->action = $action; + } + + function show() + { + $this->out->elementStart('ul', 'applications xoxo'); + + $cnt = 0; + + while ($this->application->fetch()) { + $cnt++; + if($cnt > APPS_PER_PAGE) { + break; + } + $this->showapplication(); + } + + $this->out->elementEnd('ul'); + + return $cnt; + } + + function showApplication() + { + $this->out->elementStart('li', array('class' => 'application', + 'id' => 'oauthclient-' . $this->application->id)); + + $user = common_current_user(); + + $this->out->raw($this->application->name); + + $this->out->elementEnd('li'); + } + + /* Override this in subclasses. */ + + function showOwnerControls() + { + return; + } + + function highlight($text) + { + return htmlspecialchars($text); + } +} diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index e5fb8727ba..4b5059540d 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -116,6 +116,9 @@ class ConnectSettingsNav extends Widget _('Updates by SMS')); } + $menu['applicationsettings'] = array(_('Applications'), + _('OAuth connected applications')); + foreach ($menu as $menuaction => $menudesc) { $this->action->menuItem(common_local_url($menuaction), $menudesc[0], @@ -131,4 +134,3 @@ class ConnectSettingsNav extends Widget } - diff --git a/lib/router.php b/lib/router.php index 6b87ed27f6..9b2aa025ef 100644 --- a/lib/router.php +++ b/lib/router.php @@ -140,11 +140,13 @@ class Router // settings - foreach (array('profile', 'avatar', 'password', 'im', + foreach (array('profile', 'avatar', 'password', 'im', 'application', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } + $m->connect('settings/oauthclients', array('action' => 'oauthclients')); + // search foreach (array('group', 'people', 'notice') as $s) { From 9d958fd539ecb74356013c11a2fd2c4c8b6a0ed1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Nov 2009 19:02:18 -0800 Subject: [PATCH 030/130] Reorganized the OAuth app URLs and more work on the register app workflow --- actions/{oauthclients.php => apps.php} | 4 +- actions/newapplication.php | 202 ++++++++++++++++ ...tings.php => oauthconnectionssettings.php} | 4 +- lib/applicationeditform.php | 215 ++++++++++++++++++ lib/connectsettingsaction.php | 8 +- lib/router.php | 15 +- 6 files changed, 436 insertions(+), 12 deletions(-) rename actions/{oauthclients.php => apps.php} (96%) create mode 100644 actions/newapplication.php rename actions/{applicationsettings.php => oauthconnectionssettings.php} (96%) create mode 100644 lib/applicationeditform.php diff --git a/actions/oauthclients.php b/actions/apps.php similarity index 96% rename from actions/oauthclients.php rename to actions/apps.php index 9a29e158e1..d4cea1e3e9 100644 --- a/actions/oauthclients.php +++ b/actions/apps.php @@ -45,7 +45,7 @@ require_once INSTALLDIR . '/lib/connectsettingsaction.php'; * @see SettingsAction */ -class OauthClientsAction extends ConnectSettingsAction +class AppsAction extends ConnectSettingsAction { /** * Title of the page @@ -55,7 +55,7 @@ class OauthClientsAction extends ConnectSettingsAction function title() { - return _('Applications using %%site_name%%'); + return _('OAuth applications'); } /** diff --git a/actions/newapplication.php b/actions/newapplication.php new file mode 100644 index 0000000000..a78a856b18 --- /dev/null +++ b/actions/newapplication.php @@ -0,0 +1,202 @@ +. + * + * @category Applications + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Add a new application + * + * This is the form for adding a new application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class NewApplicationAction extends Action +{ + var $msg; + + function title() + { + return _('New Application'); + } + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to create a group.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the group. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->trySave(); + } else { + $this->showForm(); + } + } + + function showForm($msg=null) + { + $this->msg = $msg; + $this->showPage(); + } + + function showContent() + { + $form = new ApplicationEditForm($this); + $form->show(); + } + + function showPageNotice() + { + if ($this->msg) { + $this->element('p', 'error', $this->msg); + } else { + $this->element('p', 'instructions', + _('Use this form to register a new application.')); + } + } + + function trySave() + { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('application'); + $callback_url = $this->trimmed('callback_url'); + $this->type = $this->trimmed('type'); + $this->access_type = $this->trimmed('access_type'); + + if (!is_null($name) && mb_strlen($name) > 255) { + $this->showForm(_('Name is too long (max 255 chars).')); + return; + } else if (User_group::descriptionTooLong($description)) { + $this->showForm(sprintf( + _('description is too long (max %d chars).'), + Oauth_application::maxDescription())); + return; + } elseif (!is_null($source_url) + && (strlen($source_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Source URL is not valid.')); + return; + } elseif (!is_null($homepage) + && (strlen($homepage) > 0) + && !Validate::uri( + $homepage, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (!is_null($callback_url) + && (strlen($callback_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } + + $cur = common_current_user(); + + // Checked in prepare() above + + assert(!is_null($cur)); + + $app = new Oauth_application(); + + $app->query('BEGIN'); + + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $souce_url; + $app->organization = $organization; + $app->homepage = $homepage; + $app->callback_url = $callback_url; + $app->type = $type; + $app->access_type = $access_type; + + // generate consumer key and secret + + $app->created = common_sql_now(); + + $result = $app->insert(); + + if (!$result) { + common_log_db_error($group, 'INSERT', __FILE__); + $this->serverError(_('Could not create application.')); + } + + $group->query('COMMIT'); + + common_redirect($group->homeUrl(), 303); + + } + +} + diff --git a/actions/applicationsettings.php b/actions/oauthconnectionssettings.php similarity index 96% rename from actions/applicationsettings.php rename to actions/oauthconnectionssettings.php index 16c571feee..6ec9f70273 100644 --- a/actions/applicationsettings.php +++ b/actions/oauthconnectionssettings.php @@ -46,7 +46,7 @@ require_once INSTALLDIR . '/lib/applicationlist.php'; * @see SettingsAction */ -class ApplicationSettingsAction extends ConnectSettingsAction +class OauthconnectionssettingsAction extends ConnectSettingsAction { /** * Title of the page @@ -95,7 +95,7 @@ class ApplicationSettingsAction extends ConnectSettingsAction } $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, - $this->page, 'applicationsettings', + $this->page, 'connectionssettings', array('nickname' => $this->user->nickname)); } diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php new file mode 100644 index 0000000000..3fd45876a7 --- /dev/null +++ b/lib/applicationeditform.php @@ -0,0 +1,215 @@ +. + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/form.php'; + +/** + * Form for editing an application + * + * @category Form + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + */ + +class ApplicationEditForm extends Form +{ + /** + * group for user to join + */ + + var $application = null; + + /** + * Constructor + * + * @param Action $out output channel + * @param User_group $group group to join + */ + + function __construct($out=null, $application=null) + { + parent::__construct($out); + + $this->application = $application; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + if ($this->application) { + return 'form_application_edit-' . $this->application->id; + } else { + return 'form_application_add'; + } + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + if ($this->application) { + return common_local_url('editapplication', + array('id' => $this->application->id)); + } else { + return common_local_url('newapplication'); + } + } + + /** + * Name of the form + * + * @return void + */ + + function formLegend() + { + $this->out->element('legend', null, _('Register a new application')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + if ($this->application) { + $id = $this->application->id; + $name = $this->application->name; + $description = $this->application->description; + $source_url = $this->application->source_url; + $organization = $this->application->organization; + $homepage = $this->application->homepage; + $callback_url = $this->application->callback_url; + $this->type = $this->application->type; + $this->access_type = $this->application->access_type; + } else { + $id = ''; + $name = ''; + $description = ''; + $source_url = ''; + $organization = ''; + $homepage = ''; + $callback_url = ''; + $this->type = ''; + $this->access_type = ''; + } + + $this->out->elementStart('ul', 'form_data'); + $this->out->elementStart('li'); + + $this->out->hidden('application_id', $id); + $this->out->input('name', _('Name'), + ($this->out->arg('name')) ? $this->out->arg('name') : $name); + + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('description', _('Description'), + ($this->out->arg('Description')) ? $this->out->arg('discription') : $description); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('source_url', _('Source URL'), + ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, + _('URL of the homepage of this application')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('Organization', _('Organization'), + ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization, + _('Organization responsible for this application')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('homepage', _('Homepage'), + ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, + _('URL of the homepage of the organization')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('callback_url', ('Callback URL'), + ($this->out->arg('callback_url')) ? $this->out->arg('callback_url') : $callback_url, + _('URL to redirect to after authentication')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('type', _('Application type'), + ($this->out->arg('type')) ? $this->out->arg('type') : $type, + _('Type of application, browser or desktop')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input('access_type', _('Default access'), + ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type, + _('Default access for this application: read-write, or read-only')); + $this->out->elementEnd('li'); + + $this->out->elementEnd('ul'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save')); + } +} diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index 4b5059540d..b9c14799e0 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -115,9 +115,11 @@ class ConnectSettingsNav extends Widget array(_('SMS'), _('Updates by SMS')); } - - $menu['applicationsettings'] = array(_('Applications'), - _('OAuth connected applications')); + + $menu['oauthconnectionssettings'] = array( + _('Connections'), + _('Authorized connected applications') + ); foreach ($menu as $menuaction => $menudesc) { $this->action->menuItem(common_local_url($menuaction), diff --git a/lib/router.php b/lib/router.php index 9b2aa025ef..7b65ae215c 100644 --- a/lib/router.php +++ b/lib/router.php @@ -140,13 +140,11 @@ class Router // settings - foreach (array('profile', 'avatar', 'password', 'im', 'application', + foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } - - $m->connect('settings/oauthclients', array('action' => 'oauthclients')); - + // search foreach (array('group', 'people', 'notice') as $s) { @@ -636,12 +634,19 @@ class Router // user stuff foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', + 'nudge', 'all', 'foaf', 'xrds', 'apps', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } + + $m->connect('apps/new', array('action' => 'newapplication')); + + $m->connect(':nickname/apps/edit', + array('action' => 'editapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}') + ); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', From 035c475b45959057099c503d2cdcff8c8145e198 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 13 Nov 2009 19:10:38 -0800 Subject: [PATCH 031/130] It might help if I checkd in statusnet.ini. --- classes/statusnet.ini | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6ce4495be0..0cbe60a5a5 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -39,6 +39,7 @@ code = K [consumer] consumer_key = 130 +consumer_secret = 130 seed = 130 created = 142 modified = 384 @@ -348,6 +349,35 @@ created = 142 tag = K notice_id = K +[oauth_application] +id = 129 +owner = 129 +consumer_key = 130 +name = 130 +description = 2 +icon = 130 +source_url = 2 +organization = 2 +homepage = 2 +callback_url = 130 +type = 17 +access_type = 17 +created = 142 +modified = 384 + +[oauth_application__keys] +id = N + +[oauth_application_user] +profile_id = 129 +application_id = 129 +access_type = 17 +created = 142 + +[oauth_application_user__keys] +profile_id = K +application_id = K + [profile] id = 129 nickname = 130 From 3c2b05d222a55cd1e148f3f887bf55e924898f1b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Nov 2009 16:58:49 -0800 Subject: [PATCH 032/130] Workflow for registering new OAuth apps pretty much done. --- actions/apps.php | 63 +++++- actions/editapplication.php | 246 +++++++++++++++++++++ actions/newapplication.php | 133 ++++++++---- actions/oauthconnectionssettings.php | 13 ++ actions/showapplication.php | 306 +++++++++++++++++++++++++++ classes/Consumer.php | 16 +- classes/Oauth_application.php | 44 ++++ db/statusnet.sql | 2 +- lib/applicationeditform.php | 135 +++++++++--- lib/applicationlist.php | 46 +++- lib/default.php | 2 + lib/router.php | 27 ++- 12 files changed, 950 insertions(+), 83 deletions(-) create mode 100644 actions/editapplication.php create mode 100644 actions/showapplication.php diff --git a/actions/apps.php b/actions/apps.php index d4cea1e3e9..e6500599f7 100644 --- a/actions/apps.php +++ b/actions/apps.php @@ -31,7 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/lib/settingsaction.php'; +require_once INSTALLDIR . '/lib/applicationlist.php'; /** * Show a user's registered OAuth applications @@ -45,8 +46,23 @@ require_once INSTALLDIR . '/lib/connectsettingsaction.php'; * @see SettingsAction */ -class AppsAction extends ConnectSettingsAction +class AppsAction extends SettingsAction { + var $page = 0; + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to list your applications.')); + return false; + } + + return true; + } + /** * Title of the page * @@ -79,6 +95,49 @@ class AppsAction extends ConnectSettingsAction { $user = common_current_user(); + $offset = ($this->page - 1) * APPS_PER_PAGE; + $limit = APPS_PER_PAGE + 1; + + $application = new Oauth_application(); + $application->owner = $user->id; + $application->limit($offset, $limit); + $application->orderBy('created DESC'); + $application->find(); + + $cnt = 0; + + if ($application) { + $al = new ApplicationList($application, $user, $this); + $cnt = $al->show(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + } + + $this->element('a', + array('href' => common_local_url( + 'newapplication', + array('nickname' => $user->nickname) + ) + ), + 'Register a new application »'); + + $this->pagination( + $this->page > 1, + $cnt > APPS_PER_PAGE, + $this->page, + 'apps', + array('nickname' => $user->nickname) + ); + } + + function showEmptyListMessage() + { + $message = sprintf(_('You have not registered any applications yet.')); + + $this->elementStart('div', 'guide'); + $this->raw(common_markup_to_html($message)); + $this->elementEnd('div'); } /** diff --git a/actions/editapplication.php b/actions/editapplication.php new file mode 100644 index 0000000000..3af482844f --- /dev/null +++ b/actions/editapplication.php @@ -0,0 +1,246 @@ +. + * + * @category Applications + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Edit the details of an OAuth application + * + * This is the form for editing an application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class EditApplicationAction extends OwnerDesignAction +{ + var $msg = null; + + var $app = null; + + function title() + { + return _('Edit Application'); + } + + /** + * Prepare to run + */ + + function prepare($args) + { + parent::prepare($args); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to edit an application.')); + return false; + } + + $id = (int)$this->arg('id'); + $this->app = Oauth_application::staticGet($id); + + if (!$this->app) { + $this->clientError(_('No such application.')); + return false; + } + + return true; + } + + /** + * Handle the request + * + * On GET, show the form. On POST, try to save the group. + * + * @param array $args unused + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } else { + $this->showForm(); + } + } + + function showForm($msg=null) + { + $this->msg = $msg; + $this->showPage(); + } + + function showContent() + { + $form = new ApplicationEditForm($this, $this->app); + $form->show(); + } + + function showPageNotice() + { + if (!empty($this->msg)) { + $this->element('p', 'error', $this->msg); + } else { + $this->element('p', 'instructions', + _('Use this form to edit your application.')); + } + } + + function trySave() + { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('homepage'); + $callback_url = $this->trimmed('callback_url'); + $type = $this->arg('app_type'); + $access_type = $this->arg('access_type'); + + if (empty($name)) { + $this->showForm(_('Name is required.')); + return; + } elseif (mb_strlen($name) > 255) { + $this->showForm(_('Name is too long (max 255 chars).')); + return; + } elseif (empty($description)) { + $this->showForm(_('Description is required.')); + return; + } elseif (Oauth_application::descriptionTooLong($description)) { + $this->showForm(sprintf( + _('Description is too long (max %d chars).'), + Oauth_application::maxDescription())); + return; + } elseif (empty($source_url)) { + $this->showForm(_('Source URL is required.')); + return; + } elseif ((strlen($source_url) > 0) + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Source URL is not valid.')); + return; + } elseif (empty($organization)) { + $this->showForm(_('Organization is required.')); + return; + } elseif (mb_strlen($organization) > 255) { + $this->showForm(_('Organization is too long (max 255 chars).')); + return; + } elseif (empty($homepage)) { + $this->showForm(_('Organization homepage is required.')); + return; + } elseif ((strlen($homepage) > 0) + && !Validate::uri( + $homepage, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + return; + } elseif (strlen($callback_url) > 0 + && !Validate::uri( + $source_url, + array('allowed_schemes' => array('http', 'https')) + ) + ) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } + + $cur = common_current_user(); + + // Checked in prepare() above + + assert(!is_null($cur)); + + $orig = clone($this->app); + + $this->app->name = $name; + $this->app->description = $description; + $this->app->source_url = $source_url; + $this->app->organization = $organization; + $this->app->homepage = $homepage; + $this->app->callback_url = $callback_url; + $this->app->type = $type; + + if ($access_type == 'r') { + $this->app->setAccessFlags(true, false); + } else { + $this->app->setAccessFlags(true, true); + } + + $result = $this->app->update($orig); + + if (!$result) { + common_log_db_error($app, 'UPDATE', __FILE__); + $this->serverError(_('Could not update application.')); + } + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } + +} + diff --git a/actions/newapplication.php b/actions/newapplication.php index a78a856b18..9d8635270a 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -43,7 +43,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class NewApplicationAction extends Action +class NewApplicationAction extends OwnerDesignAction { var $msg; @@ -61,7 +61,7 @@ class NewApplicationAction extends Action parent::prepare($args); if (!common_logged_in()) { - $this->clientError(_('You must be logged in to create a group.')); + $this->clientError(_('You must be logged in to register an application.')); return false; } @@ -81,8 +81,19 @@ class NewApplicationAction extends Action function handle($args) { parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->trySave(); + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } else { $this->showForm(); } @@ -112,55 +123,73 @@ class NewApplicationAction extends Action function trySave() { - $name = $this->trimmed('name'); - $description = $this->trimmed('description'); - $source_url = $this->trimmed('source_url'); - $organization = $this->trimmed('organization'); - $homepage = $this->trimmed('application'); - $callback_url = $this->trimmed('callback_url'); - $this->type = $this->trimmed('type'); - $this->access_type = $this->trimmed('access_type'); - - if (!is_null($name) && mb_strlen($name) > 255) { + $name = $this->trimmed('name'); + $description = $this->trimmed('description'); + $source_url = $this->trimmed('source_url'); + $organization = $this->trimmed('organization'); + $homepage = $this->trimmed('homepage'); + $callback_url = $this->trimmed('callback_url'); + $type = $this->arg('app_type'); + $access_type = $this->arg('access_type'); + + if (empty($name)) { + $this->showForm(_('Name is required.')); + return; + } elseif (mb_strlen($name) > 255) { $this->showForm(_('Name is too long (max 255 chars).')); return; - } else if (User_group::descriptionTooLong($description)) { + } elseif (empty($description)) { + $this->showForm(_('Description is required.')); + return; + } elseif (Oauth_application::descriptionTooLong($description)) { $this->showForm(sprintf( - _('description is too long (max %d chars).'), + _('Description is too long (max %d chars).'), Oauth_application::maxDescription())); return; - } elseif (!is_null($source_url) - && (strlen($source_url) > 0) + } elseif (empty($source_url)) { + $this->showForm(_('Source URL is required.')); + return; + } elseif ((strlen($source_url) > 0) && !Validate::uri( $source_url, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Source URL is not valid.')); return; - } elseif (!is_null($homepage) - && (strlen($homepage) > 0) + } elseif (empty($organization)) { + $this->showForm(_('Organization is required.')); + return; + } elseif (mb_strlen($organization) > 255) { + $this->showForm(_('Organization is too long (max 255 chars).')); + return; + } elseif (empty($homepage)) { + $this->showForm(_('Organization homepage is required.')); + return; + } elseif ((strlen($homepage) > 0) && !Validate::uri( $homepage, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Homepage is not a valid URL.')); - return; - } elseif (!is_null($callback_url) - && (strlen($callback_url) > 0) + return; + } elseif (empty($callback_url)) { + $this->showForm(_('Callback is required.')); + return; + } elseif (strlen($callback_url) > 0 && !Validate::uri( $source_url, array('allowed_schemes' => array('http', 'https')) ) - ) + ) { $this->showForm(_('Callback URL is not valid.')); return; } - + $cur = common_current_user(); // Checked in prepare() above @@ -171,31 +200,53 @@ class NewApplicationAction extends Action $app->query('BEGIN'); - $app->name = $name; - $app->owner = $cur->id; - $app->description = $description; - $app->source_url = $souce_url; + $app->name = $name; + $app->owner = $cur->id; + $app->description = $description; + $app->source_url = $source_url; $app->organization = $organization; - $app->homepage = $homepage; + $app->homepage = $homepage; $app->callback_url = $callback_url; - $app->type = $type; - $app->access_type = $access_type; - + $app->type = $type; + + // Yeah, I dunno why I chose bit flags. I guess so I could + // copy this value directly to Oauth_application_user + // access_type which I think does need bit flags -- Z + + if ($access_type == 'r') { + $app->setAccessFlags(true, false); + } else { + $app->setAccessFlags(true, true); + } + + $app->created = common_sql_now(); + // generate consumer key and secret - - $app->created = common_sql_now(); + + $consumer = Consumer::generateNew(); + + $result = $consumer->insert(); + + if (!$result) { + common_log_db_error($consumer, 'INSERT', __FILE__); + $this->serverError(_('Could not create application.')); + } + + $app->consumer_key = $consumer->consumer_key; $result = $app->insert(); if (!$result) { - common_log_db_error($group, 'INSERT', __FILE__); + common_log_db_error($app, 'INSERT', __FILE__); $this->serverError(_('Could not create application.')); + $app->query('ROLLBACK'); } - - $group->query('COMMIT'); - common_redirect($group->homeUrl(), 303); - + $app->query('COMMIT'); + + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } } diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 6ec9f70273..e4b5af1586 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -132,4 +132,17 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $this->elementEnd('div'); } + function showSections() + { + $cur = common_current_user(); + + $this->element('h2', null, 'Developers'); + $this->elementStart('p'); + $this->raw(_('Developers can edit the registration settings for their applications ')); + $this->element('a', + array('href' => common_local_url('apps', array('nickname' => $cur->nickname))), + 'here.'); + $this->elementEnd('p'); + } + } diff --git a/actions/showapplication.php b/actions/showapplication.php new file mode 100644 index 0000000000..6b8eff4a60 --- /dev/null +++ b/actions/showapplication.php @@ -0,0 +1,306 @@ +. + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Show an OAuth application + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ShowApplicationAction extends OwnerDesignAction +{ + /** + * Application to show + */ + + var $application = null; + + /** + * User who owns the app + */ + + var $owner = null; + + + var $msg = null; + + var $success = null; + + /** + * Load attributes based on database arguments + * + * Loads all the DB stuff + * + * @param array $args $_REQUEST array + * + * @return success flag + */ + + function prepare($args) + { + parent::prepare($args); + + $id = (int)$this->arg('id'); + + $this->application = Oauth_application::staticGet($id); + $this->owner = User::staticGet($this->application->owner); + + if (!common_logged_in()) { + $this->clientError(_('You must be logged in to view an application.')); + return false; + } + + if (empty($this->application)) { + $this->clientError(_('No such application.'), 404); + return false; + } + + $cur = common_current_user(); + + if ($cur->id != $this->owner->id) { + $this->clientError(_('You are not the owner of this application.'), 401); + } + + return true; + } + + /** + * Handle the request + * + * Shows info about the app + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + if ($this->arg('reset')) { + $this->resetKey(); + } + } else { + $this->showPage(); + } + } + + /** + * Title of the page + * + * @return string title of the page + */ + + function title() + { + if (!empty($this->application->name)) { + return 'Application: ' . $this->application->name; + } + } + + function showPageNotice() + { + if (!empty($this->msg)) { + $this->element('div', ($this->success) ? 'success' : 'error', $this->msg); + } + } + + function showContent() + { + + $cur = common_current_user(); + + $this->elementStart('div', 'entity_actions'); + + $this->element('a', + array('href' => + common_local_url( + 'editapplication', + array( + 'nickname' => $this->owner->nickname, + 'id' => $this->application->id + ) + ) + ), 'Edit application'); + + $this->elementStart('form', array( + 'id' => 'forma_reset_key', + 'class' => 'form_reset_key', + 'method' => 'POST', + 'action' => common_local_url('showapplication', + array('nickname' => $cur->nickname, + 'id' => $this->application->id)))); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->submit('reset', _('Reset Consumer key/secret')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + + $this->elementEnd('div'); + + $consumer = $this->application->getConsumer(); + + $this->elementStart('div', 'entity-application'); + + $this->elementStart('ul', 'entity_application_details'); + + $this->elementStart('li', 'entity_application_name'); + $this->element('span', array('class' => 'big'), $this->application->name); + $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); + $this->elementEnd('li'); + + $this->element('li', 'entity_application_description', $this->application->description); + + $this->elementStart('li', 'entity_application_statistics'); + + $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; + $profile = Profile::staticGet($this->application->owner); + $userCnt = 0; // XXX: count how many users use the app + + $this->raw(sprintf( + _('Created by %1$s - %2$s access by default - %3$d users.'), + $profile->getBestName(), + $defaultAccess, + $userCnt + )); + + $this->elementEnd('li'); + + $this->elementEnd('ul'); + + $this->elementStart('dl', 'entity_consumer_key'); + $this->element('dt', null, _('Consumer key')); + $this->element('dd', 'label', $consumer->consumer_key); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_consumer_secret'); + $this->element('dt', null, _('Consumer secret')); + $this->element('dd', 'label', $consumer->consumer_secret); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_request_token_url'); + $this->element('dt', null, _('Request token URL')); + $this->element('dd', 'label', common_local_url('oauthrequesttoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_access_token_url'); + $this->element('dt', null, _('Access token URL')); + $this->element('dd', 'label', common_local_url('oauthaccesstoken')); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_authorize_url'); + $this->element('dt', null, _('Authorize URL')); + $this->element('dd', 'label', common_local_url('oauthauthorize')); + $this->elementEnd('dl'); + + $this->element('p', 'oauth-signature-note', + '*We support hmac-sha1 signatures. We do not support the plaintext signature method.'); + + $this->elementEnd('div'); + + $this->elementStart('div', 'entity-list-apps'); + $this->element('a', + array( + 'href' => common_local_url( + 'apps', + array('nickname' => $this->owner->nickname) + ) + ), + 'View your applications'); + $this->elementEnd('div'); + } + + function resetKey() + { + $this->application->query('BEGIN'); + + $consumer = $this->application->getConsumer(); + $result = $consumer->delete(); + + if (!$result) { + common_log_db_error($consumer, 'DELETE', __FILE__); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $consumer = Consumer::generateNew(); + + $result = $consumer->insert(); + + if (!$result) { + common_log_db_error($consumer, 'INSERT', __FILE__); + $this->application->query('ROLLBACK'); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $orig = clone($this->application); + $this->application->consumer_key = $consumer->consumer_key; + $result = $this->application->update($orig); + + if (!$result) { + common_log_db_error($application, 'UPDATE', __FILE__); + $this->application->query('ROLLBACK'); + $this->success = false; + $this->msg = ('Unable to reset consumer key and secret.'); + $this->showPage(); + return; + } + + $this->application->query('COMMIT'); + + $this->success = true; + $this->msg = ('Consumer key and secret reset.'); + $this->showPage(); + } + +} + diff --git a/classes/Consumer.php b/classes/Consumer.php index d17f183a88..ad64a8491b 100644 --- a/classes/Consumer.php +++ b/classes/Consumer.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Consumer extends Memcached_DataObject +class Consumer extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -22,4 +22,18 @@ class Consumer extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + static function generateNew() + { + $cons = new Consumer(); + $rand = common_good_rand(16); + + $cons->seed = $rand; + $cons->consumer_key = md5(time() + $rand); + $cons->consumer_secret = md5(md5(time() + time() + $rand)); + $cons->created = common_sql_now(); + + return $cons; + } + } diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index e2862bf97f..ef1bbf6d95 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -31,4 +31,48 @@ class Oauth_application extends Memcached_DataObject } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + // Bit flags + public static $readAccess = 1; + public static $writeAccess = 2; + + public static $browser = 1; + public static $desktop = 2; + + function getConsumer() + { + return Consumer::staticGet('consumer_key', $this->consumer_key); + } + + static function maxDesc() + { + $desclimit = common_config('application', 'desclimit'); + // null => use global limit (distinct from 0!) + if (is_null($desclimit)) { + $desclimit = common_config('site', 'textlimit'); + } + return $desclimit; + } + + static function descriptionTooLong($desc) + { + $desclimit = self::maxDesc(); + return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit)); + } + + function setAccessFlags($read, $write) + { + if ($read) { + $this->access_type |= self::$readAccess; + } else { + $this->access_type &= ~self::$readAccess; + } + + if ($write) { + $this->access_type |= self::$writeAccess; + } else { + $this->access_type &= ~self::$writeAccess; + } + } + } diff --git a/db/statusnet.sql b/db/statusnet.sql index 03e6115e5f..a2740b60cc 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -219,7 +219,7 @@ create table oauth_application ( organization varchar(255) comment 'name of the organization running the application', homepage varchar(255) comment 'homepage for the organization', callback_url varchar(255) not null comment 'url to redirect to after authentication', - type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop', + type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 3fd45876a7..ed187ba0b4 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -100,11 +100,16 @@ class ApplicationEditForm extends Form function action() { - if ($this->application) { + $cur = common_current_user(); + + if (!empty($this->application)) { return common_local_url('editapplication', - array('id' => $this->application->id)); + array('id' => $this->application->id, + 'nickname' => $cur->nickname) + ); } else { - return common_local_url('newapplication'); + return common_local_url('newapplication', + array('nickname' => $cur->nickname)); } } @@ -116,7 +121,7 @@ class ApplicationEditForm extends Form function formLegend() { - $this->out->element('legend', null, _('Register a new application')); + $this->out->element('legend', null, _('Edit application')); } /** @@ -130,7 +135,7 @@ class ApplicationEditForm extends Form if ($this->application) { $id = $this->application->id; $name = $this->application->name; - $description = $this->application->description; + $description = $this->application->description; $source_url = $this->application->source_url; $organization = $this->application->organization; $homepage = $this->application->homepage; @@ -151,34 +156,46 @@ class ApplicationEditForm extends Form $this->out->elementStart('ul', 'form_data'); $this->out->elementStart('li'); - + $this->out->hidden('application_id', $id); + $this->out->hidden('token', common_session_token()); + $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); - + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); - $this->out->input('description', _('Description'), - ($this->out->arg('Description')) ? $this->out->arg('discription') : $description); + + $maxDesc = Oauth_application::maxDesc(); + if ($maxDesc > 0) { + $descInstr = sprintf(_('Describe your application in %d chars'), + $maxDesc); + } else { + $descInstr = _('Describe your application'); + } + $this->out->textarea('description', _('Description'), + ($this->out->arg('description')) ? $this->out->arg('description') : $description, + $descInstr); + $this->out->elementEnd('li'); - + $this->out->elementStart('li'); $this->out->input('source_url', _('Source URL'), ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url, _('URL of the homepage of this application')); - $this->out->elementEnd('li'); + $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('Organization', _('Organization'), - ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization, + $this->out->input('organization', _('Organization'), + ($this->out->arg('organization')) ? $this->out->arg('organization') : $organization, _('Organization responsible for this application')); $this->out->elementEnd('li'); $this->out->elementStart('li'); $this->out->input('homepage', _('Homepage'), ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage, - _('URL of the homepage of the organization')); + _('URL for the homepage of the organization')); $this->out->elementEnd('li'); $this->out->elementStart('li'); @@ -188,17 +205,86 @@ class ApplicationEditForm extends Form $this->out->elementEnd('li'); $this->out->elementStart('li'); - $this->out->input('type', _('Application type'), - ($this->out->arg('type')) ? $this->out->arg('type') : $type, - _('Type of application, browser or desktop')); + + $attrs = array('name' => 'app_type', + 'type' => 'radio', + 'id' => 'app_type-browser', + 'class' => 'radio', + 'value' => Oauth_application::$browser); + + // Default to Browser + + if ($this->application->type == Oauth_application::$browser + || empty($this->applicaiton->type)) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'app_type-browser', + 'class' => 'radio'), + _('Browser')); + + $attrs = array('name' => 'app_type', + 'type' => 'radio', + 'id' => 'app_type-dekstop', + 'class' => 'radio', + 'value' => Oauth_application::$desktop); + + if ($this->application->type == Oauth_application::$desktop) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'app_type-desktop', + 'class' => 'radio'), + _('Desktop')); + $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); - + $this->out->elementStart('li'); - $this->out->input('access_type', _('Default access'), - ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type, - _('Default access for this application: read-write, or read-only')); + + $attrs = array('name' => 'default_access_type', + 'type' => 'radio', + 'id' => 'default_access_type-r', + 'class' => 'radio', + 'value' => 'r'); + + // default to read-only access + + if ($this->application->access_type & Oauth_application::$readAccess + || empty($this->application->access_type)) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'default_access_type-ro', + 'class' => 'radio'), + _('Read-only')); + + $attrs = array('name' => 'default_access_type', + 'type' => 'radio', + 'id' => 'default_access_type-rw', + 'class' => 'radio', + 'value' => 'rw'); + + if ($this->application->access_type & Oauth_application::$readAccess + && $this->application->access_type & Oauth_application::$writeAccess + ) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'default_access_type-rw', + 'class' => 'radio'), + _('Read-write')); + $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write')); + $this->out->elementEnd('li'); - + $this->out->elementEnd('ul'); } @@ -210,6 +296,7 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('submit', _('Save')); + $this->out->submit('save', _('Save')); + $this->out->submit('cancel', _('Cancel')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index fed784bb63..3141ea9741 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -20,7 +20,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @copyright 2008-2009 StatusNet, Inc. @@ -39,7 +39,7 @@ define('APPS_PER_PAGE', 20); /** * Widget to show a list of OAuth applications * - * @category Public + * @category Application * @package StatusNet * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 @@ -50,10 +50,10 @@ class ApplicationList extends Widget { /** Current application, application query */ var $application = null; - + /** Owner of this list */ var $owner = null; - + /** Action object using us. */ var $action = null; @@ -87,14 +87,42 @@ class ApplicationList extends Widget function showApplication() { - $this->out->elementStart('li', array('class' => 'application', - 'id' => 'oauthclient-' . $this->application->id)); $user = common_current_user(); - $this->out->raw($this->application->name); - - $this->out->elementEnd('li'); + $this->out->elementStart('li', array('class' => 'application', + 'id' => 'oauthclient-' . $this->application->id)); + + $this->out->elementStart('a', + array('href' => common_local_url( + 'showapplication', + array( + 'nickname' => $user->nickname, + 'id' => $this->application->id + ) + ), + 'class' => 'url') + ); + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + + $this->out->raw(' by '); + + $this->out->elementStart('a', + array( + 'href' => $this->application->homepage, + 'class' => 'url' + ) + ); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); + + $this->out->elementStart('p', 'note'); + $this->out->raw($this->application->description); + $this->out->elementEnd('p'); + + $this->out->elementEnd('li'); } /* Override this in subclasses. */ diff --git a/lib/default.php b/lib/default.php index e3a043de12..b6ee72279d 100644 --- a/lib/default.php +++ b/lib/default.php @@ -211,6 +211,8 @@ $default = 'uploads' => true, 'filecommand' => '/usr/bin/file', ), + 'application' => + array('desclimit' => null), 'group' => array('maxaliases' => 3, 'desclimit' => null), diff --git a/lib/router.php b/lib/router.php index 7b65ae215c..a8dbbf6d0f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -641,12 +641,29 @@ class Router array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect('apps/new', array('action' => 'newapplication')); - - $m->connect(':nickname/apps/edit', - array('action' => 'editapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}') + $m->connect(':nickname/apps', + array('action' => 'apps'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/show/:id', + array('action' => 'showapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') ); + $m->connect(':nickname/apps/new', + array('action' => 'newapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}')); + $m->connect(':nickname/apps/edit/:id', + array('action' => 'editapplication'), + array('nickname' => '['.NICKNAME_FMT.']{1,64}', + 'id' => '[0-9]+') + ); + + $m->connect('oauth/request_token', + array('action' => 'oauthrequesttoken')); + $m->connect('oauth/access_token', + array('action' => 'oauthaccesstoken')); + $m->connect('oauth/authorize', + array('action' => 'oauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', From 1e5b2a497e3c70e4af5f93e2326c93beed15fed1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 16 Nov 2009 18:12:39 -0800 Subject: [PATCH 033/130] Added session token checking. --- actions/newapplication.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actions/newapplication.php b/actions/newapplication.php index 9d8635270a..ec0f2e7af2 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -84,6 +84,13 @@ class NewApplicationAction extends OwnerDesignAction if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + $cur = common_current_user(); if ($this->arg('cancel')) { From 48e5f2b3c5164aa9e47289e5b243e2e1189b71ef Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 01:55:57 -0800 Subject: [PATCH 034/130] Add icons/icon upload to Oauth apps --- actions/editapplication.php | 81 +++++++++++++++++---------- actions/newapplication.php | 100 ++++++++++++++++++++++++++-------- actions/showapplication.php | 9 ++- classes/Oauth_application.php | 13 +++++ lib/applicationeditform.php | 43 ++++++++++++++- lib/applicationlist.php | 4 ++ 6 files changed, 193 insertions(+), 57 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index 3af482844f..6b8dd501c9 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -81,7 +81,7 @@ class EditApplicationAction extends OwnerDesignAction /** * Handle the request * - * On GET, show the form. On POST, try to save the group. + * On GET, show the form. On POST, try to save the app. * * @param array $args unused * @@ -91,31 +91,49 @@ class EditApplicationAction extends OwnerDesignAction function handle($args) { parent::handle($args); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost($args); + } else { + $this->showForm(); + } + } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } + function handlePost($args) + { + // Workaround for PHP returning empty $_POST and $_FILES when POST + // length > post_max_size in php.ini - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } - } else { - $this->showForm(); + if (empty($_FILES) + && empty($_POST) + && ($_SERVER['CONTENT_LENGTH'] > 0) + ) { + $msg = _('The server was unable to handle that much POST ' . + 'data (%s bytes) due to its current configuration.'); + $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); + return; } + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -149,7 +167,7 @@ class EditApplicationAction extends OwnerDesignAction $homepage = $this->trimmed('homepage'); $callback_url = $this->trimmed('callback_url'); $type = $this->arg('app_type'); - $access_type = $this->arg('access_type'); + $access_type = $this->arg('default_access_type'); if (empty($name)) { $this->showForm(_('Name is required.')); @@ -214,6 +232,7 @@ class EditApplicationAction extends OwnerDesignAction // Checked in prepare() above assert(!is_null($cur)); + assert(!is_null($this->app)); $orig = clone($this->app); @@ -225,16 +244,18 @@ class EditApplicationAction extends OwnerDesignAction $this->app->callback_url = $callback_url; $this->app->type = $type; - if ($access_type == 'r') { - $this->app->setAccessFlags(true, false); - } else { - $this->app->setAccessFlags(true, true); - } - $result = $this->app->update($orig); + common_debug("access_type = $access_type"); + + if ($access_type == 'r') { + $this->app->access_type = 1; + } else { + $this->app->access_type = 3; + } + if (!$result) { - common_log_db_error($app, 'UPDATE', __FILE__); + common_log_db_error($this->app, 'UPDATE', __FILE__); $this->serverError(_('Could not update application.')); } diff --git a/actions/newapplication.php b/actions/newapplication.php index ec0f2e7af2..a0e61d288c 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -71,7 +71,7 @@ class NewApplicationAction extends OwnerDesignAction /** * Handle the request * - * On GET, show the form. On POST, try to save the group. + * On GET, show the form. On POST, try to save the app. * * @param array $args unused * @@ -83,29 +83,46 @@ class NewApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } - - $cur = common_current_user(); - - if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + $this->handlePost($args); } else { $this->showForm(); } } + function handlePost($args) + { + // Workaround for PHP returning empty $_POST and $_FILES when POST + // length > post_max_size in php.ini + + if (empty($_FILES) + && empty($_POST) + && ($_SERVER['CONTENT_LENGTH'] > 0) + ) { + $msg = _('The server was unable to handle that much POST ' . + 'data (%s bytes) due to its current configuration.'); + $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); + return; + } + + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } + + $cur = common_current_user(); + + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } + } + function showForm($msg=null) { $this->msg = $msg; @@ -130,14 +147,14 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); $homepage = $this->trimmed('homepage'); $callback_url = $this->trimmed('callback_url'); $type = $this->arg('app_type'); - $access_type = $this->arg('access_type'); + $access_type = $this->arg('default_access_type'); if (empty($name)) { $this->showForm(_('Name is required.')); @@ -241,14 +258,16 @@ class NewApplicationAction extends OwnerDesignAction $app->consumer_key = $consumer->consumer_key; - $result = $app->insert(); + $this->app_id = $app->insert(); - if (!$result) { + if (!$this->app_id) { common_log_db_error($app, 'INSERT', __FILE__); $this->serverError(_('Could not create application.')); $app->query('ROLLBACK'); } + $this->uploadLogo($app); + $app->query('COMMIT'); common_redirect(common_local_url('apps', @@ -256,5 +275,40 @@ class NewApplicationAction extends OwnerDesignAction } + /** + * Handle an image upload + * + * Does all the magic for handling an image upload, and crops the + * image by default. + * + * @return void + */ + + function uploadLogo($app) + { + if ($_FILES['app_icon']['error'] == + UPLOAD_ERR_OK) { + + try { + $imagefile = ImageFile::fromUpload('app_icon'); + } catch (Exception $e) { + common_debug("damn that sucks"); + $this->showForm($e->getMessage()); + return; + } + + $filename = Avatar::filename($app->id, + image_type_to_extension($imagefile->type), + null, + 'oauth-app-icon-'.common_timestamp()); + + $filepath = Avatar::path($filename); + + move_uploaded_file($imagefile->filepath, $filepath); + + $app->setOriginal($filename); + } + } + } diff --git a/actions/showapplication.php b/actions/showapplication.php index 6b8eff4a60..6d19b9561c 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -55,7 +55,6 @@ class ShowApplicationAction extends OwnerDesignAction var $owner = null; - var $msg = null; var $success = null; @@ -187,6 +186,14 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('ul', 'entity_application_details'); + $this->elementStart('li', 'entity_application-icon'); + + if (!empty($this->application->icon)) { + $this->element('img', array('src' => $this->application->icon)); + } + + $this->elementEnd('li'); + $this->elementStart('li', 'entity_application_name'); $this->element('span', array('class' => 'big'), $this->application->name); $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index ef1bbf6d95..d4de6d82e1 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -75,4 +75,17 @@ class Oauth_application extends Memcached_DataObject } } + function setOriginal($filename) + { + $imagefile = new ImageFile($this->id, Avatar::path($filename)); + + // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini? + // or just one and control size via CSS? --Zach + + $orig = clone($this); + $this->icon = Avatar::url($filename); + common_debug(common_log_objstring($this)); + return $this->update($orig); + } + } diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index ed187ba0b4..4d3bb06e75 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -81,6 +81,21 @@ class ApplicationEditForm extends Form } } + /** + * HTTP method used to submit the form + * + * For image data we need to send multipart/form-data + * so we set that here too + * + * @return string the method to use for submitting + */ + + function method() + { + $this->enctype = 'multipart/form-data'; + return 'post'; + } + /** * class of the form * @@ -134,6 +149,7 @@ class ApplicationEditForm extends Form { if ($this->application) { $id = $this->application->id; + $icon = $this->application->icon; $name = $this->application->name; $description = $this->application->description; $source_url = $this->application->source_url; @@ -144,6 +160,7 @@ class ApplicationEditForm extends Form $this->access_type = $this->application->access_type; } else { $id = ''; + $icon = ''; $name = ''; $description = ''; $source_url = ''; @@ -154,11 +171,31 @@ class ApplicationEditForm extends Form $this->access_type = ''; } + $this->out->hidden('token', common_session_token()); + $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li'); + + $this->out->elementStart('li'); + + if (!empty($icon)) { + $this->out->element('img', array('src' => $icon)); + } + + $this->out->element('label', array('for' => 'app_icon'), + _('Icon')); + $this->out->element('input', array('name' => 'app_icon', + 'type' => 'file', + 'id' => 'app_icon')); + $this->out->element('p', 'form_guide', _('Icon for this application')); + $this->out->element('input', array('name' => 'MAX_FILE_SIZE', + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); $this->out->hidden('application_id', $id); - $this->out->hidden('token', common_session_token()); $this->out->input('name', _('Name'), ($this->out->arg('name')) ? $this->out->arg('name') : $name); @@ -215,7 +252,7 @@ class ApplicationEditForm extends Form // Default to Browser if ($this->application->type == Oauth_application::$browser - || empty($this->applicaiton->type)) { + || empty($this->application->type)) { $attrs['checked'] = 'checked'; } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 3141ea9741..5392ddab8c 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -93,6 +93,10 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } + $this->out->elementStart('a', array('href' => common_local_url( 'showapplication', From 6472331be51bc6d0e670603b2a89fb66022f6b51 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 13:19:21 -0800 Subject: [PATCH 035/130] Stubs for API OAuth token exchange stuff --- actions/apioauthaccesstoken.php | 49 ++++++++++++++++++++++++++++++++ actions/apioauthauthorize.php | 49 ++++++++++++++++++++++++++++++++ actions/apioauthrequesttoken.php | 49 ++++++++++++++++++++++++++++++++ lib/router.php | 6 ++-- 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 actions/apioauthaccesstoken.php create mode 100644 actions/apioauthauthorize.php create mode 100644 actions/apioauthrequesttoken.php diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php new file mode 100644 index 0000000000..db82f656ad --- /dev/null +++ b/actions/apioauthaccesstoken.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Exchange an authorized OAuth request token for an access token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAccessTokenAction extends ApiAction +{ + +} diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php new file mode 100644 index 0000000000..8839d9571c --- /dev/null +++ b/actions/apioauthauthorize.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Authorize an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAuthorizeAction extends Action +{ + +} diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php new file mode 100644 index 0000000000..c1ccd4b7d7 --- /dev/null +++ b/actions/apioauthrequesttoken.php @@ -0,0 +1,49 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/api.php'; + +/** + * Get an OAuth request token + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthRequestTokenAction extends ApiAction +{ + +} diff --git a/lib/router.php b/lib/router.php index a8dbbf6d0f..0703d75970 100644 --- a/lib/router.php +++ b/lib/router.php @@ -659,11 +659,11 @@ class Router ); $m->connect('oauth/request_token', - array('action' => 'oauthrequesttoken')); + array('action' => 'apioauthrequesttoken')); $m->connect('oauth/access_token', - array('action' => 'oauthaccesstoken')); + array('action' => 'apioauthaccesstoken')); $m->connect('oauth/authorize', - array('action' => 'oauthauthorize')); + array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', From fa81a580bb9eea76e7739f37010b35e4b919f410 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 7 Jan 2010 18:33:17 -0800 Subject: [PATCH 036/130] Action for issuing a request token --- actions/apioauthrequesttoken.php | 41 ++++++++++++++- lib/apioauthstore.php | 90 ++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 lib/apioauthstore.php diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index c1ccd4b7d7..1bbd7d295b 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -32,6 +32,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Get an OAuth request token @@ -43,7 +44,45 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiOauthRequestTokenAction extends ApiAction +class ApiOauthRequestTokenAction extends Action { + /** + * Is read only? + * + * @return boolean false + */ + function isReadOnly() + { + return false; + } + + /** + * Class handler. + * + * @param array $args array of arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $server->add_signature_method($hmac_method); + + try { + $req = OAuthRequest::from_request(); + $token = $server->fetch_request_token($req); + print $token; + } catch (OAuthException $e) { + common_log(LOG_WARN, $e->getMessage()); + common_debug(var_export($req, true)); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $e->getMessage() . "\n"; + } + } } diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php new file mode 100644 index 0000000000..a92a4d6e49 --- /dev/null +++ b/lib/apioauthstore.php @@ -0,0 +1,90 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR . '/lib/oauthstore.php'; + +class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore +{ + + function lookup_consumer($consumer_key) + { + $con = Consumer::staticGet('consumer_key', $consumer_key); + + if (!$con) { + return null; + } + + return new OAuthConsumer($con->consumer_key, + $con->consumer_secret); + } + + function new_access_token($token, $consumer) + { + common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); + $rt = new Token(); + $rt->consumer_key = $consumer->key; + $rt->tok = $token->key; + $rt->type = 0; // request + if ($rt->find(true) && $rt->state == 1) { // authorized + common_debug('request token found.', __FILE__); + $at = new Token(); + $at->consumer_key = $consumer->key; + $at->tok = common_good_rand(16); + $at->secret = common_good_rand(16); + $at->type = 1; // access + $at->created = DB_DataObject_Cast::dateTime(); + if (!$at->insert()) { + $e = $at->_lastError; + common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); + return null; + } else { + common_debug('access token "'.$at->tok.'" inserted', __FILE__); + // burn the old one + $orig_rt = clone($rt); + $rt->state = 2; // used + if (!$rt->update($orig_rt)) { + return null; + } + common_debug('request token "'.$rt->tok.'" updated', __FILE__); + // Update subscription + // XXX: mixing levels here + $sub = Subscription::staticGet('token', $rt->tok); + if (!$sub) { + return null; + } + common_debug('subscription for request token found', __FILE__); + $orig_sub = clone($sub); + $sub->token = $at->tok; + $sub->secret = $at->secret; + if (!$sub->update($orig_sub)) { + return null; + } else { + common_debug('subscription updated to use access token', __FILE__); + return new OAuthToken($at->tok, $at->secret); + } + } + } else { + return null; + } + } + +} + From e9e448bcee69b0c39badf353faedb4c29af3f502 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 10 Jan 2010 21:35:46 -0800 Subject: [PATCH 037/130] Workflow for request tokens and authorizing request tokens --- actions/apioauthauthorize.php | 326 ++++++++++++++++++++++++++++++- actions/apioauthrequesttoken.php | 5 +- actions/showapplication.php | 6 +- lib/router.php | 19 +- 4 files changed, 338 insertions(+), 18 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 8839d9571c..895a0c6e53 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Authorize an OAuth request token @@ -45,5 +45,329 @@ require_once INSTALLDIR . '/lib/api.php'; class ApiOauthAuthorizeAction extends Action { + var $oauth_token; + var $callback; + var $app; + var $nickname; + var $password; + var $store; + + /** + * Is this a read-only action? + * + * @return boolean false + */ + + function isReadOnly($args) + { + return false; + } + + function prepare($args) + { + parent::prepare($args); + + common_debug(var_export($_REQUEST, true)); + + $this->nickname = $this->trimmed('nickname'); + $this->password = $this->arg('password'); + $this->oauth_token = $this->arg('oauth_token'); + $this->callback = $this->arg('oauth_callback'); + $this->store = new ApiStatusNetOAuthDataStore(); + + return true; + } + + function getApp() + { + // Look up the full req token + + $req_token = $this->store->lookup_token(null, + 'request', + $this->oauth_token); + + if (empty($req_token)) { + + common_debug("Couldn't find request token!"); + + $this->clientError(_('Bad request.')); + return; + } + + // Look up the app + + $app = new Oauth_application(); + $app->consumer_key = $req_token->consumer_key; + $result = $app->find(true); + + if (!empty($result)) { + $this->app = $app; + return true; + + } else { + common_debug("couldn't find the app!"); + return false; + } + } + + /** + * Handle input, produce output + * + * Switches on request method; either shows the form or handles its input. + * + * @param array $args $_REQUEST data + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + /* Use a session token for CSRF protection. */ + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + $this->handlePost(); + + } else { + + common_debug('ApiOauthAuthorize::handle()'); + + if (empty($this->oauth_token)) { + + common_debug("No request token found."); + + $this->clientError(_('Bad request.')); + return; + } + + if (!$this->getApp()) { + $this->clientError(_('Bad request.')); + return; + } + + common_debug("Requesting auth for app: $app->name."); + + $this->showForm(); + } + } + + function handlePost() + { + /* Use a session token for CSRF protection. */ + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if (!$this->getApp()) { + $this->clientError(_('Bad request.')); + return; + } + + // is the user already logged in? + + // check creds + + if (!common_logged_in()) { + $user = common_check_user($this->nickname, $this->password); + if (empty($user)) { + $this->showForm(_("Invalid nickname / password!")); + return; + } + } + + if ($this->arg('allow')) { + + $this->store->authorize_token($this->oauth_token); + + // if we have a callback redirect and provide the token + + if (!empty($this->callback)) { + $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; + common_redirect($target_url, 303); + } + + // otherwise inform the user that the rt was authorized + + $this->elementStart('p'); + + // XXX: Do verifier code? + + $this->raw(sprintf(_("The request token %s has been authorized. " . + 'Please exchange it for an access token.'), + $this->oauth_token)); + + $this->elementEnd('p'); + + } else if ($this->arg('deny')) { + + $this->elementStart('p'); + + $this->raw(sprintf(_("The request token %s has been denied."), + $this->oauth_token)); + + $this->elementEnd('p'); + } else { + $this->clientError(_('Unexpected form submission.')); + return; + } + } + + function showForm($error=null) + { + $this->error = $error; + $this->showPage(); + } + + function showScripts() + { + parent::showScripts(); + // $this->autofocus('nickname'); + } + + /** + * Title of the page + * + * @return string title of the page + */ + + function title() + { + return _('An application would like to connect to your account'); + } + + /** + * Show page notice + * + * Display a notice for how to use the page, or the + * error if it exists. + * + * @return void + */ + + function showPageNotice() + { + if ($this->error) { + $this->element('p', 'error', $this->error); + } else { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + + $this->raw($output); + } + } + + /** + * Shows the authorization form. + * + * @return void + */ + + function showContent() + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_login', + 'class' => 'form_settings', + 'action' => common_local_url('apioauthauthorize'))); + + $this->hidden('token', common_session_token()); + $this->hidden('oauth_token', $this->oauth_token); + $this->hidden('oauth_callback', $this->callback); + + $this->elementStart('fieldset'); + + $this->elementStart('ul'); + $this->elementStart('li'); + if (!empty($this->app->icon)) { + $this->element('img', array('src' => $this->app->icon)); + } + $this->elementEnd('li'); + $this->elementStart('li'); + + $access = ($this->app->access_type & Oauth_application::$writeAccess) ? + 'access and update' : 'access'; + + $msg = _("The application %s by %s would like " . + "the ability to %s your account data."); + + $this->raw(sprintf($msg, + $this->app->name, + $this->app->organization, + $access)); + + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementEnd('fieldset'); + + if (!common_logged_in()) { + + $this->elementStart('fieldset'); + $this->element('legend', null, _('Login')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementEnd('fieldset'); + + } + + $this->element('input', array('id' => 'deny_submit', + 'class' => 'submit', + 'name' => 'deny', + 'type' => 'submit', + 'value' => _('Deny'))); + + $this->element('input', array('id' => 'allow_submit', + 'class' => 'submit', + 'name' => 'allow', + 'type' => 'submit', + 'value' => _('Allow'))); + + $this->elementEnd('form'); + } + + /** + * Instructions for using the form + * + * For "remembered" logins, we make the user re-login when they + * try to change settings. Different instructions for this case. + * + * @return void + */ + + function getInstructions() + { + return _('Allow or deny access to your account information.'); + + } + + /** + * A local menu + * + * Shows different login/register actions. + * + * @return void + */ + + function showLocalNav() + { + } } diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 1bbd7d295b..53aca6b96b 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -31,7 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; require_once INSTALLDIR . '/lib/apioauthstore.php'; /** @@ -70,6 +69,7 @@ class ApiOauthRequestTokenAction extends Action $datastore = new ApiStatusNetOAuthDataStore(); $server = new OAuthServer($datastore); $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $server->add_signature_method($hmac_method); try { @@ -77,8 +77,7 @@ class ApiOauthRequestTokenAction extends Action $token = $server->fetch_request_token($req); print $token; } catch (OAuthException $e) { - common_log(LOG_WARN, $e->getMessage()); - common_debug(var_export($req, true)); + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); header('HTTP/1.1 401 Unauthorized'); header('Content-Type: text/html; charset=utf-8'); print $e->getMessage() . "\n"; diff --git a/actions/showapplication.php b/actions/showapplication.php index 6d19b9561c..5156fa6f0c 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -231,17 +231,17 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('dl', 'entity_request_token_url'); $this->element('dt', null, _('Request token URL')); - $this->element('dd', 'label', common_local_url('oauthrequesttoken')); + $this->element('dd', 'label', common_local_url('apioauthrequesttoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_access_token_url'); $this->element('dt', null, _('Access token URL')); - $this->element('dd', 'label', common_local_url('oauthaccesstoken')); + $this->element('dd', 'label', common_local_url('apioauthaccesstoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_authorize_url'); $this->element('dt', null, _('Authorize URL')); - $this->element('dd', 'label', common_local_url('oauthauthorize')); + $this->element('dd', 'label', common_local_url('apioauthauthorize')); $this->elementEnd('dl'); $this->element('p', 'oauth-signature-note', diff --git a/lib/router.php b/lib/router.php index 0703d75970..420f5a0a10 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,7 +50,8 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe'); + 'postnotice', 'updateprofile', 'finishremotesubscribe', + 'apioauthrequesttoken', 'apioauthaccesstoken'); static function get() { @@ -144,7 +145,7 @@ class Router 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } - + // search foreach (array('group', 'people', 'notice') as $s) { @@ -640,11 +641,11 @@ class Router array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - - $m->connect(':nickname/apps', + + $m->connect(':nickname/apps', array('action' => 'apps'), array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/show/:id', + $m->connect(':nickname/apps/show/:id', array('action' => 'showapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}', 'id' => '[0-9]+') @@ -652,18 +653,14 @@ class Router $m->connect(':nickname/apps/new', array('action' => 'newapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/edit/:id', + $m->connect(':nickname/apps/edit/:id', array('action' => 'editapplication'), array('nickname' => '['.NICKNAME_FMT.']{1,64}', 'id' => '[0-9]+') ); - $m->connect('oauth/request_token', - array('action' => 'apioauthrequesttoken')); - $m->connect('oauth/access_token', - array('action' => 'apioauthaccesstoken')); $m->connect('oauth/authorize', - array('action' => 'apioauthauthorize')); + array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { $m->connect(':nickname/'.$a.'/:tag', From c473a39a7da07fbe5b80fec4c08111a554691c3a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 10 Jan 2010 23:03:30 -0800 Subject: [PATCH 038/130] Associate request tokens with OAuth apps and app users --- actions/apioauthauthorize.php | 64 +++++++++++++++++++++++------- classes/Oauth_application_user.php | 24 ++++++++++- classes/statusnet.ini | 4 ++ db/statusnet.sql | 5 ++- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 895a0c6e53..48d5087efc 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -125,19 +125,12 @@ class ApiOauthAuthorizeAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - /* Use a session token for CSRF protection. */ - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } $this->handlePost(); } else { - common_debug('ApiOauthAuthorize::handle()'); + // XXX: make better error messages if (empty($this->oauth_token)) { @@ -160,7 +153,7 @@ class ApiOauthAuthorizeAction extends Action function handlePost() { - /* Use a session token for CSRF protection. */ + // check session token for CSRF protection. $token = $this->trimmed('token'); @@ -175,25 +168,66 @@ class ApiOauthAuthorizeAction extends Action return; } - // is the user already logged in? - // check creds + $user = null; + if (!common_logged_in()) { $user = common_check_user($this->nickname, $this->password); if (empty($user)) { $this->showForm(_("Invalid nickname / password!")); return; } - } + } else { + $user = common_current_user(); + } if ($this->arg('allow')) { + // mark the req token as authorized + $this->store->authorize_token($this->oauth_token); + // Check to see if there was a previous token associated + // with this user/app and kill it. If you're doing this you + // probably don't want any old tokens anyway. + + $appUser = Oauth_application_user::getByKeys($user, $this->app); + + if (!empty($appUser)) { + $result = $appUser->delete(); + + if (!$result) { + common_log_db_error($appUser, 'DELETE', __FILE__); + throw new ServerException(_('DB error deleting OAuth app user.')); + return; + } + } + + // associated the new req token with the user and the app + + $appUser = new Oauth_application_user(); + + $appUser->profile_id = $user->id; + $appUser->application_id = $this->app->id; + $appUser->access_type = $this->app->access_type; + $appUser->token = $this->oauth_token; + $appUser->created = common_sql_now(); + + $result = $appUser->insert(); + + if (!$result) { + common_log_db_error($appUser, 'INSERT', __FILE__); + throw new ServerException(_('DB error inserting OAuth app user.')); + return; + } + // if we have a callback redirect and provide the token if (!empty($this->callback)) { + + // XXX: Need better way to build this redirect url. + $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; common_redirect($target_url, 303); } @@ -202,7 +236,7 @@ class ApiOauthAuthorizeAction extends Action $this->elementStart('p'); - // XXX: Do verifier code? + // XXX: Do OAuth 1.0a verifier code? $this->raw(sprintf(_("The request token %s has been authorized. " . 'Please exchange it for an access token.'), @@ -233,7 +267,9 @@ class ApiOauthAuthorizeAction extends Action function showScripts() { parent::showScripts(); - // $this->autofocus('nickname'); + if (!common_logged_in()) { + $this->autofocus('nickname'); + } } /** diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index 9e45ece25f..e4c018f219 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -13,12 +13,34 @@ class Oauth_application_user extends Memcached_DataObject public $profile_id; // int(4) primary_key not_null public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) + public $token; // varchar(255) + public $secret; // varchar(255) + public $verifier; // varchar(255) public $created; // datetime not_null + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ function staticGet($k,$v=NULL) { - return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); + return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + static function getByKeys($user, $app) + { + if (empty($user) || empty($app)) { + return null; + } + + $oau = new Oauth_application_user(); + + $oau->profile_id = $user->id; + $oau->application_id = $app->id; + $oau->limit(1); + + $result = $oau->find(true); + + return empty($result) ? null : $oau; + } + } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 0cbe60a5a5..43f6c4466d 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -372,7 +372,11 @@ id = N profile_id = 129 application_id = 129 access_type = 17 +token = 2 +secret = 2 +verifier = 2 created = 142 +modified = 384 [oauth_application_user__keys] profile_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index a2740b60cc..eb47060674 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -229,8 +229,11 @@ create table oauth_application_user ( profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', + token varchar(255) comment 'authorization token', + secret varchar(255) comment 'token secret', + verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', - + modified timestamp comment 'date this record was modified', constraint primary key (profile_id, application_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; From a0b84387737b016168eb3b9a1c6dee1980724f66 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 01:11:50 -0800 Subject: [PATCH 039/130] Exchanging authorized request tokens for access tokens working --- actions/apioauthaccesstoken.php | 60 ++++++++++++++++++++++++++++- classes/Oauth_application.php | 14 +++++++ lib/apioauthstore.php | 68 +++++++++++++++++++++++---------- 3 files changed, 119 insertions(+), 23 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index db82f656ad..9b99724d0c 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Exchange an authorized OAuth request token for an access token @@ -43,7 +43,63 @@ require_once INSTALLDIR . '/lib/api.php'; * @link http://status.net/ */ -class ApiOauthAccessTokenAction extends ApiAction +class ApiOauthAccessTokenAction extends Action { + /** + * Is read only? + * + * @return boolean false + */ + function isReadOnly() + { + return false; + } + + /** + * Class handler. + * + * @param array $args array of arguments + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + + $server->add_signature_method($hmac_method); + + $atok = null; + + try { + $req = OAuthRequest::from_request(); + $atok = $server->fetch_access_token($req); + + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->outputError($e->getMessage()); + return; + } + + if (empty($atok)) { + common_debug('couldn\'t get access token.'); + $this->outputError("Badness."); + return; + } + + print $atok; + } + + function outputError($msg) + { + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; + } } + diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index d4de6d82e1..5df8b9459c 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -88,4 +88,18 @@ class Oauth_application extends Memcached_DataObject return $this->update($orig); } + static function getByConsumerKey($key) + { + if (empty($key)) { + return null; + } + + $app = new Oauth_application(); + $app->consumer_key = $key; + $app->limit(1); + $result = $app->find(true); + + return empty($result) ? null : $app; + } + } diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index a92a4d6e49..290ce89730 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -39,19 +39,45 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore function new_access_token($token, $consumer) { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); - $rt = new Token(); + + $rt = new Token(); $rt->consumer_key = $consumer->key; $rt->tok = $token->key; $rt->type = 0; // request - if ($rt->find(true) && $rt->state == 1) { // authorized + + $app = Oauth_application::getByConsumerKey($consumer->key); + + if (empty($app)) { + common_debug("empty app!"); + } + + if ($rt->find(true) && $rt->state == 1) { // authorized common_debug('request token found.', __FILE__); - $at = new Token(); + + // find the associated user of the app + + $appUser = new Oauth_application_user(); + $appUser->application_id = $app->id; + $appUser->token = $rt->tok; + $result = $appUser->find(true); + + if (!empty($result)) { + common_debug("Oath app user found."); + } else { + common_debug("Oauth app user not found."); + return null; + } + + // go ahead and make the access token + + $at = new Token(); $at->consumer_key = $consumer->key; $at->tok = common_good_rand(16); $at->secret = common_good_rand(16); $at->type = 1; // access $at->created = DB_DataObject_Cast::dateTime(); - if (!$at->insert()) { + + if (!$at->insert()) { $e = $at->_lastError; common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); return null; @@ -64,23 +90,23 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore return null; } common_debug('request token "'.$rt->tok.'" updated', __FILE__); - // Update subscription - // XXX: mixing levels here - $sub = Subscription::staticGet('token', $rt->tok); - if (!$sub) { - return null; - } - common_debug('subscription for request token found', __FILE__); - $orig_sub = clone($sub); - $sub->token = $at->tok; - $sub->secret = $at->secret; - if (!$sub->update($orig_sub)) { - return null; - } else { - common_debug('subscription updated to use access token', __FILE__); - return new OAuthToken($at->tok, $at->secret); - } - } + + // update the token from req to access for the user + + $orig = clone($appUser); + $appUser->token = $at->tok; + $result = $appUser->update($orig); + + if (empty($result)) { + common_debug('couldn\'t update OAuth app user.'); + return null; + } + + // Okay, good + + return new OAuthToken($at->tok, $at->secret); + } + } else { return null; } From c2337ab47c183ab9758f5db8632ac9cd480a282a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 12:17:36 -0800 Subject: [PATCH 040/130] Decided we didn't need to keep the token secret in the Oauth_application_user record --- classes/Oauth_application_user.php | 1 - classes/statusnet.ini | 1 - db/statusnet.sql | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index e4c018f219..a05371f563 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) public $token; // varchar(255) - public $secret; // varchar(255) public $verifier; // varchar(255) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 43f6c4466d..b358bbf60a 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -373,7 +373,6 @@ profile_id = 129 application_id = 129 access_type = 17 token = 2 -secret = 2 verifier = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index eb47060674..c3160bcf87 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -229,8 +229,7 @@ create table oauth_application_user ( profile_id integer not null comment 'user of the application' references profile (id), application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', - token varchar(255) comment 'authorization token', - secret varchar(255) comment 'token secret', + token varchar(255) comment 'request or access token', verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', From 11bd98025c1e41921359b634461772d22a1c059f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 12:52:56 -0800 Subject: [PATCH 041/130] Issue a warning when someone tries to exchange an unauthorized or otherwise bad req token for an access token. --- actions/apioauthaccesstoken.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 9b99724d0c..67359d765d 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -88,11 +88,10 @@ class ApiOauthAccessTokenAction extends Action if (empty($atok)) { common_debug('couldn\'t get access token.'); - $this->outputError("Badness."); - return; + print "Token exchange failed. Has the request token been authorized?\n"; + } else { + print $atok; } - - print $atok; } function outputError($msg) From c78937537ed17eabb665ec6e4344b564799cbccc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 14:11:43 -0800 Subject: [PATCH 042/130] Better detial in connected OAuth applications list --- actions/oauthconnectionssettings.php | 32 +++++++++--- classes/Profile.php | 9 ++-- lib/applicationlist.php | 74 +++++++++++++++++++--------- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index e4b5af1586..56e7b02fba 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -48,6 +48,16 @@ require_once INSTALLDIR . '/lib/applicationlist.php'; class OauthconnectionssettingsAction extends ConnectSettingsAction { + + var $page = null; + + function prepare($args) + { + parent::prepare($args); + $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; + return true; + } + /** * Title of the page * @@ -59,6 +69,11 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction return _('Connected Applications'); } + function isReadOnly($args) + { + return true; + } + /** * Instructions for use * @@ -86,13 +101,16 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $application = $profile->getApplications($offset, $limit); - if ($application) { - $al = new ApplicationList($application, $this->user, $this); - $cnt = $al->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); - } - } + $cnt == 0; + + if (!empty($application)) { + $al = new ApplicationList($application, $user, $this, true); + $cnt = $al->show(); + } + + if ($cnt == 0) { + $this->showEmptyListMessage(); + } $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, $this->page, 'connectionssettings', diff --git a/classes/Profile.php b/classes/Profile.php index 687215b11b..fef2a21710 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -355,10 +355,11 @@ class Profile extends Memcached_DataObject function getApplications($offset = 0, $limit = null) { $qry = - 'SELECT oauth_application_user.* ' . - 'FROM oauth_application_user ' . - 'WHERE profile_id = %d ' . - 'ORDER BY created DESC '; + 'SELECT a.* ' . + 'FROM oauth_application_user u, oauth_application a ' . + 'WHERE u.profile_id = %d ' . + 'AND a.id = u.application_id ' . + 'ORDER BY u.created DESC '; if ($offset > 0) { if (common_config('db','type') == 'pgsql') { diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 5392ddab8c..e305437f4c 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -57,13 +57,14 @@ class ApplicationList extends Widget /** Action object using us. */ var $action = null; - function __construct($application, $owner=null, $action=null) + function __construct($application, $owner=null, $action=null, $connections = false) { parent::__construct($action); $this->application = $application; $this->owner = $owner; $this->action = $action; + $this->connections = $connections; } function show() @@ -97,36 +98,65 @@ class ApplicationList extends Widget $this->out->element('img', array('src' => $this->application->icon)); } - $this->out->elementStart('a', - array('href' => common_local_url( - 'showapplication', - array( - 'nickname' => $user->nickname, - 'id' => $this->application->id - ) - ), - 'class' => 'url') - ); + if (!$this->connections) { + + $this->out->elementStart('a', + array('href' => + common_local_url('showapplication', + array('nickname' => $user->nickname, + 'id' => $this->application->id)), + 'class' => 'url') + ); $this->out->raw($this->application->name); $this->out->elementEnd('a'); - - $this->out->raw(' by '); - + } else { $this->out->elementStart('a', - array( - 'href' => $this->application->homepage, - 'class' => 'url' - ) - ); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); + array('href' => $this->application->source_url, + 'class' => 'url')); - $this->out->elementStart('p', 'note'); + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } + + $this->out->raw(' by '); + + $this->out->elementStart('a', + array( + 'href' => $this->application->homepage, + 'class' => 'url' + ) + ); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); + + $this->out->elementStart('p', 'note'); $this->out->raw($this->application->description); $this->out->elementEnd('p'); + $this->out->elementEnd('li'); + + if ($this->connections) { + + $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); + + if (empty($appUser)) { + common_debug("empty appUser!"); + } + + $this->out->elementStart('li'); + + $access = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; + + $txt = 'Approved ' . common_exact_date($appUser->modified) . + " $access for access."; + + $this->out->raw($txt); $this->out->elementEnd('li'); + + // XXX: Add revoke access button + } } /* Override this in subclasses. */ From 4fb9b43aa2c174e0b5ac3e260cafd2b5fcfa9368 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 22:46:35 +0000 Subject: [PATCH 043/130] Updated markup for application edit form submits --- lib/applicationeditform.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 4d3bb06e75..f8fcb3e3f5 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -333,7 +333,9 @@ class ApplicationEditForm extends Form function formActions() { - $this->out->submit('save', _('Save')); - $this->out->submit('cancel', _('Cancel')); + $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary', + 'cancel', _('Cancel')); + $this->out->submit('save', _('Save'), 'submit form_action-secondary', + 'save', _('Save')); } } From 61f71a4a597bb2eab2b56a80a71e867c1539739d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 22:54:46 +0000 Subject: [PATCH 044/130] Updated markup for application registration and view links --- actions/apps.php | 7 +++++-- actions/showapplication.php | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actions/apps.php b/actions/apps.php index e6500599f7..7c7b24570f 100644 --- a/actions/apps.php +++ b/actions/apps.php @@ -114,13 +114,16 @@ class AppsAction extends SettingsAction } } + $this->elementStart('p', array('id' => 'application_register')); $this->element('a', array('href' => common_local_url( 'newapplication', array('nickname' => $user->nickname) - ) + ), + 'class' => 'more' ), - 'Register a new application »'); + 'Register a new application'); + $this->elementEnd('p'); $this->pagination( $this->page > 1, diff --git a/actions/showapplication.php b/actions/showapplication.php index 5156fa6f0c..3e191148ae 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -249,16 +249,16 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('div'); - $this->elementStart('div', 'entity-list-apps'); + $this->elementStart('p', array('id' => 'application_action')); $this->element('a', array( 'href' => common_local_url( 'apps', - array('nickname' => $this->owner->nickname) - ) + array('nickname' => $this->owner->nickname)), + 'class' => 'more' ), 'View your applications'); - $this->elementEnd('div'); + $this->elementEnd('p'); } function resetKey() From c8a4d0d6c2cafe1d3d4285c4b634f1dc52e91d8b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 11 Jan 2010 23:51:12 +0000 Subject: [PATCH 045/130] Updated markup for application details --- actions/showapplication.php | 70 +++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 3e191148ae..33bc51f931 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -152,7 +152,8 @@ class ShowApplicationAction extends OwnerDesignAction $cur = common_current_user(); $this->elementStart('div', 'entity_actions'); - + $this->elementStart('ul'); + $this->elementStart('li'); $this->element('a', array('href' => common_local_url( @@ -163,7 +164,9 @@ class ShowApplicationAction extends OwnerDesignAction ) ) ), 'Edit application'); + $this->elementEnd('li'); + $this->elementStart('li'); $this->elementStart('form', array( 'id' => 'forma_reset_key', 'class' => 'form_reset_key', @@ -177,32 +180,39 @@ class ShowApplicationAction extends OwnerDesignAction $this->submit('reset', _('Reset Consumer key/secret')); $this->elementEnd('fieldset'); $this->elementEnd('form'); - + $this->elementEnd('li'); + $this->elementEnd('ul'); $this->elementEnd('div'); $consumer = $this->application->getConsumer(); - $this->elementStart('div', 'entity-application'); + $this->elementStart('div', 'entity_application'); + $this->element('h2', null, _('Application profile')); + $this->elementStart('dl', 'entity_depiction'); + $this->element('dt', null, _('Icon')); + $this->elementStart('dd'); + if (!empty($this->application->icon)) { + $this->element('img', array('src' => $this->application->icon)); + } + $this->elementEnd('dd'); + $this->elementEnd('dl'); - $this->elementStart('ul', 'entity_application_details'); - - $this->elementStart('li', 'entity_application-icon'); - - if (!empty($this->application->icon)) { - $this->element('img', array('src' => $this->application->icon)); - } - - $this->elementEnd('li'); - - $this->elementStart('li', 'entity_application_name'); - $this->element('span', array('class' => 'big'), $this->application->name); + $this->elementStart('dl', 'entity_fn'); + $this->element('dt', null, _('Name')); + $this->elementStart('dd'); + $this->element('span', null, $this->application->name); $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); - $this->elementEnd('li'); + $this->elementEnd('dd'); + $this->elementEnd('dl'); - $this->element('li', 'entity_application_description', $this->application->description); - - $this->elementStart('li', 'entity_application_statistics'); + $this->elementStart('dl', 'entity_note'); + $this->element('dt', null, _('Description')); + $this->element('dd', null, $this->application->description); + $this->elementEnd('dl'); + $this->elementStart('dl', 'entity_statistics'); + $this->element('dt', null, _('Statistics')); + $this->elementStart('dd'); $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; $profile = Profile::staticGet($this->application->owner); @@ -214,39 +224,39 @@ class ShowApplicationAction extends OwnerDesignAction $defaultAccess, $userCnt )); + $this->elementEnd('dd'); + $this->elementEnd('dl'); + $this->elementEnd('div'); - $this->elementEnd('li'); - - $this->elementEnd('ul'); - + $this->elementStart('div', array('id' => 'entity_data')); + $this->element('h2', null, _('Application info')); $this->elementStart('dl', 'entity_consumer_key'); $this->element('dt', null, _('Consumer key')); - $this->element('dd', 'label', $consumer->consumer_key); + $this->element('dd', null, $consumer->consumer_key); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_consumer_secret'); $this->element('dt', null, _('Consumer secret')); - $this->element('dd', 'label', $consumer->consumer_secret); + $this->element('dd', null, $consumer->consumer_secret); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_request_token_url'); $this->element('dt', null, _('Request token URL')); - $this->element('dd', 'label', common_local_url('apioauthrequesttoken')); + $this->element('dd', null, common_local_url('apioauthrequesttoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_access_token_url'); $this->element('dt', null, _('Access token URL')); - $this->element('dd', 'label', common_local_url('apioauthaccesstoken')); + $this->element('dd', null, common_local_url('apioauthaccesstoken')); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_authorize_url'); $this->element('dt', null, _('Authorize URL')); - $this->element('dd', 'label', common_local_url('apioauthauthorize')); + $this->element('dd', null, common_local_url('apioauthauthorize')); $this->elementEnd('dl'); $this->element('p', 'oauth-signature-note', - '*We support hmac-sha1 signatures. We do not support the plaintext signature method.'); - + '* We support hmac-sha1 signatures. We do not support the plaintext signature method.'); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); From 0b90f7645e5bd54c99c6fb7c1fe105e183b9e8c1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:01:45 +0000 Subject: [PATCH 046/130] Updated class for application list --- lib/applicationlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index e305437f4c..23c727bd61 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -69,7 +69,7 @@ class ApplicationList extends Widget function show() { - $this->out->elementStart('ul', 'applications xoxo'); + $this->out->elementStart('ul', array('id' => 'applications')); $cnt = 0; From c2ffd6612887675e55a9d9398517e23ee95c9117 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:02:25 +0000 Subject: [PATCH 047/130] Updated markup for application details page. Similar to user/group profile page. --- actions/showapplication.php | 90 +++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 33bc51f931..db28395c29 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -151,63 +151,33 @@ class ShowApplicationAction extends OwnerDesignAction $cur = common_current_user(); - $this->elementStart('div', 'entity_actions'); - $this->elementStart('ul'); - $this->elementStart('li'); - $this->element('a', - array('href' => - common_local_url( - 'editapplication', - array( - 'nickname' => $this->owner->nickname, - 'id' => $this->application->id - ) - ) - ), 'Edit application'); - $this->elementEnd('li'); - - $this->elementStart('li'); - $this->elementStart('form', array( - 'id' => 'forma_reset_key', - 'class' => 'form_reset_key', - 'method' => 'POST', - 'action' => common_local_url('showapplication', - array('nickname' => $cur->nickname, - 'id' => $this->application->id)))); - - $this->elementStart('fieldset'); - $this->hidden('token', common_session_token()); - $this->submit('reset', _('Reset Consumer key/secret')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->elementEnd('div'); - $consumer = $this->application->getConsumer(); - $this->elementStart('div', 'entity_application'); + $this->elementStart('div', 'entity_profile vcard'); $this->element('h2', null, _('Application profile')); $this->elementStart('dl', 'entity_depiction'); $this->element('dt', null, _('Icon')); $this->elementStart('dd'); if (!empty($this->application->icon)) { - $this->element('img', array('src' => $this->application->icon)); + $this->element('img', array('src' => $this->application->icon, + 'class' => 'photo logo')); } $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_fn'); $this->element('dt', null, _('Name')); - $this->elementStart('dd'); - $this->element('span', null, $this->application->name); - $this->raw(sprintf(_(' by %1$s'), $this->application->organization)); - $this->elementEnd('dd'); + $this->element('dd', 'fn', $this->application->name); + $this->elementEnd('dl'); + + $this->elementStart('dl', 'entity_org'); + $this->element('dt', null, _('Organization')); + $this->element('dd', 'org', $this->application->organization); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_note'); $this->element('dt', null, _('Description')); - $this->element('dd', null, $this->application->description); + $this->element('dd', 'note', $this->application->description); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_statistics'); @@ -228,7 +198,41 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('dl'); $this->elementEnd('div'); - $this->elementStart('div', array('id' => 'entity_data')); + $this->elementStart('div', 'entity_actions'); + $this->element('h2', null, _('Application actions')); + $this->elementStart('ul'); + $this->elementStart('li', 'entity_edit'); + $this->element('a', + array('href' => + common_local_url( + 'editapplication', + array( + 'nickname' => $this->owner->nickname, + 'id' => $this->application->id + ) + ) + ), 'Edit'); + $this->elementEnd('li'); + + $this->elementStart('li', 'entity_reset_keysecret'); + $this->elementStart('form', array( + 'id' => 'forma_reset_key', + 'class' => 'form_reset_key', + 'method' => 'POST', + 'action' => common_local_url('showapplication', + array('nickname' => $cur->nickname, + 'id' => $this->application->id)))); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->submit('reset', _('Reset key & secret')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->elementEnd('div'); + + $this->elementStart('div', 'entity_data'); $this->element('h2', null, _('Application info')); $this->elementStart('dl', 'entity_consumer_key'); $this->element('dt', null, _('Consumer key')); @@ -255,8 +259,8 @@ class ShowApplicationAction extends OwnerDesignAction $this->element('dd', null, common_local_url('apioauthauthorize')); $this->elementEnd('dl'); - $this->element('p', 'oauth-signature-note', - '* We support hmac-sha1 signatures. We do not support the plaintext signature method.'); + $this->element('p', 'note', + _('Note: We support hmac-sha1 signatures. We do not support the plaintext signature method.')); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); From ba0c82b391ad3ec71bb7efe60b03f4f95f9ecaf5 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:13:36 +0000 Subject: [PATCH 048/130] Added anchors to application source and homepage --- actions/showapplication.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index db28395c29..f2ff8b9002 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -167,12 +167,20 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('dl', 'entity_fn'); $this->element('dt', null, _('Name')); - $this->element('dd', 'fn', $this->application->name); + $this->elementStart('dd'); + $this->element('a', array('href' => $this->application->source_url, + 'class' => 'url fn'), + $this->application->name); + $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_org'); $this->element('dt', null, _('Organization')); - $this->element('dd', 'org', $this->application->organization); + $this->elementStart('dd'); + $this->element('a', array('href' => $this->application->homepage, + 'class' => 'url'), + $this->application->organization); + $this->elementEnd('dd'); $this->elementEnd('dl'); $this->elementStart('dl', 'entity_note'); From d998c4b1b89492e02d56ba8a395387078472f163 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:29:09 +0000 Subject: [PATCH 049/130] Fixed tabbing --- lib/applicationlist.php | 87 ++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 23c727bd61..b404767ba0 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -69,7 +69,7 @@ class ApplicationList extends Widget function show() { - $this->out->elementStart('ul', array('id' => 'applications')); + $this->out->elementStart('ul', 'applications'); $cnt = 0; @@ -94,69 +94,58 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); - if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); - } + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } - if (!$this->connections) { + if (!$this->connections) { + $this->out->elementStart('a', + array('href' => common_local_url('showapplication', + array('nickname' => $user->nickname, + 'id' => $this->application->id)), + 'class' => 'url')); - $this->out->elementStart('a', - array('href' => - common_local_url('showapplication', - array('nickname' => $user->nickname, - 'id' => $this->application->id)), - 'class' => 'url') - ); + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } else { + $this->out->elementStart('a', array('href' => $this->application->source_url, + 'class' => 'url')); - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); - } else { - $this->out->elementStart('a', - array('href' => $this->application->source_url, - 'class' => 'url')); + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + } - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); - } + $this->out->raw(' by '); - $this->out->raw(' by '); + $this->out->elementStart('a', array('href' => $this->application->homepage, + 'class' => 'url')); + $this->out->raw($this->application->organization); + $this->out->elementEnd('a'); - $this->out->elementStart('a', - array( - 'href' => $this->application->homepage, - 'class' => 'url' - ) - ); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); - - $this->out->elementStart('p', 'note'); + $this->out->elementStart('p', 'note'); $this->out->raw($this->application->description); $this->out->elementEnd('p'); - $this->out->elementEnd('li'); + if ($this->connections) { + $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); - if ($this->connections) { + if (empty($appUser)) { + common_debug("empty appUser!"); + } - $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); + $this->out->elementStart('li'); - if (empty($appUser)) { - common_debug("empty appUser!"); - } + $access = ($this->application->access_type & Oauth_application::$writeAccess) + ? 'read-write' : 'read-only'; - $this->out->elementStart('li'); + $txt = 'Approved ' . common_exact_date($appUser->modified) . + " $access for access."; - $access = ($this->application->access_type & Oauth_application::$writeAccess) - ? 'read-write' : 'read-only'; + $this->out->raw($txt); + $this->out->elementEnd('li'); - $txt = 'Approved ' . common_exact_date($appUser->modified) . - " $access for access."; - - $this->out->raw($txt); - $this->out->elementEnd('li'); - - // XXX: Add revoke access button - } + // XXX: Add revoke access button + } } /* Override this in subclasses. */ From 8e91e053923f395d4dc0ad7f3f328953c9b17145 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 11 Jan 2010 17:30:56 -0800 Subject: [PATCH 050/130] Make API auth handle OAuth requests w/access tokens --- lib/apiauth.php | 114 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index 7102764cba..3229ab19fd 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -28,7 +28,7 @@ * @author Evan Prodromou * @author mEDI * @author Sarven Capadisli - * @author Zach Copley + * @author Zach Copley * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -39,6 +39,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; +require_once INSTALLDIR . '/lib/apioauthstore.php'; /** * Actions extending this class will require auth @@ -52,6 +53,8 @@ require_once INSTALLDIR . '/lib/api.php'; class ApiAuthAction extends ApiAction { + var $access_token; + var $oauth_access_type; /** * Take arguments for running, and output basic auth header if needed @@ -67,12 +70,119 @@ class ApiAuthAction extends ApiAction parent::prepare($args); if ($this->requiresAuth()) { - $this->checkBasicAuthUser(); + + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); + + if (!empty($this->access_token)) { + $this->checkOAuthRequest(); + } else { + $this->checkBasicAuthUser(); + } } return true; } + function checkOAuthRequest() + { + common_debug("We have an OAuth request."); + + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + + $server->add_signature_method($hmac_method); + + $this->cleanRequest(); + + try { + + $req = OAuthRequest::from_request(); + $server->verify_request($req); + + common_debug("Good OAuth request!"); + + $app = Oauth_application::getByConsumerKey($this->consumer_key); + + if (empty($app)) { + + // this should really not happen + common_log(LOG_WARN, + "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + + throw new OAuthException('No application for that consumer key.'); + } + + $appUser = Oauth_application_user::staticGet('token', + $this->access_token); + + // XXX: check that app->id and appUser->application_id and consumer all + // match? + + if (!empty($appUser)) { + + // read or read-write + $this->oauth_access_type = $appUser->access_type; + + // If access_type == 0 we have either a request token + // or a bad / revoked access token + + if ($this->oauth_access_type != 0) { + + $this->auth_user = User::staticGet('id', $appUser->profile_id); + + $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . + "application '%s' (id: %d)."; + + common_log(LOG_INFO, sprintf($msg, + $this->auth_user->nickname, + $this->auth_user->id, + $app->name, + $app->id)); + return true; + } else { + throw new OAuthException('Bad access token.'); + } + } else { + + // also should not happen + throw new OAuthException('No user for that token.'); + } + + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->showOAuthError($e->getMessage()); + exit(); + } + } + + function showOAuthError($msg) + { + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; + } + + function cleanRequest() + { + // kill evil effects of magical slashing + + if(get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + + // strip out the p param added in index.php + + // XXX: should we strip anything else? Or alternatively + // only allow a known list of params? + + unset($_GET['p']); + unset($_POST['p']); + } + /** * Does this API resource require authentication? * From 40c6d09c9f3a3a1167015782ac4972681ce98772 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:36:08 +0000 Subject: [PATCH 051/130] Added missing end tag --- lib/applicationlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index b404767ba0..b1dcc39a96 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -110,7 +110,6 @@ class ApplicationList extends Widget } else { $this->out->elementStart('a', array('href' => $this->application->source_url, 'class' => 'url')); - $this->out->raw($this->application->name); $this->out->elementEnd('a'); } @@ -125,6 +124,7 @@ class ApplicationList extends Widget $this->out->elementStart('p', 'note'); $this->out->raw($this->application->description); $this->out->elementEnd('p'); + $this->out->elementEnd('li'); if ($this->connections) { $appUser = Oauth_application_user::getByKeys($this->owner, $this->application); From 34cc03c61792792e9b23e9a28e7730268b22d3d1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:41:38 +0000 Subject: [PATCH 052/130] Moved application image inside the anchor --- lib/applicationlist.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index b1dcc39a96..8961da4355 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -94,10 +94,6 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); - if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); - } - if (!$this->connections) { $this->out->elementStart('a', array('href' => common_local_url('showapplication', @@ -105,15 +101,18 @@ class ApplicationList extends Widget 'id' => $this->application->id)), 'class' => 'url')); - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); } else { $this->out->elementStart('a', array('href' => $this->application->source_url, 'class' => 'url')); - $this->out->raw($this->application->name); - $this->out->elementEnd('a'); } + if (!empty($this->application->icon)) { + $this->out->element('img', array('src' => $this->application->icon)); + } + + $this->out->raw($this->application->name); + $this->out->elementEnd('a'); + $this->out->raw(' by '); $this->out->elementStart('a', array('href' => $this->application->homepage, From 276c4a2a231cce0b449e97d2bdba0a822b898e08 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:44:15 +0000 Subject: [PATCH 053/130] Added vcard and photo classes --- lib/applicationlist.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 8961da4355..6ca210537a 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -94,6 +94,7 @@ class ApplicationList extends Widget $this->out->elementStart('li', array('class' => 'application', 'id' => 'oauthclient-' . $this->application->id)); + $this->out->elementStart('span', 'vcard author'); if (!$this->connections) { $this->out->elementStart('a', array('href' => common_local_url('showapplication', @@ -107,11 +108,13 @@ class ApplicationList extends Widget } if (!empty($this->application->icon)) { - $this->out->element('img', array('src' => $this->application->icon)); + $this->out->element('img', array('src' => $this->application->icon, + 'class' => 'photo')); } $this->out->raw($this->application->name); $this->out->elementEnd('a'); + $this->out->elementEnd('span'); $this->out->raw(' by '); From a009052036250d33b38329dd0363dd42652e89da Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 01:52:59 +0000 Subject: [PATCH 054/130] A little minimization --- lib/applicationlist.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 6ca210537a..15c2d588a3 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -109,23 +109,20 @@ class ApplicationList extends Widget if (!empty($this->application->icon)) { $this->out->element('img', array('src' => $this->application->icon, - 'class' => 'photo')); + 'class' => 'photo avatar')); } - $this->out->raw($this->application->name); + $this->out->element('span', 'fn', $this->application->name); $this->out->elementEnd('a'); $this->out->elementEnd('span'); $this->out->raw(' by '); - $this->out->elementStart('a', array('href' => $this->application->homepage, - 'class' => 'url')); - $this->out->raw($this->application->organization); - $this->out->elementEnd('a'); + $this->out->element('a', array('href' => $this->application->homepage, + 'class' => 'url'), + $this->application->organization); - $this->out->elementStart('p', 'note'); - $this->out->raw($this->application->description); - $this->out->elementEnd('p'); + $this->out->element('p', 'note', $this->application->description); $this->out->elementEnd('li'); if ($this->connections) { From 8d02a897dc6269b44d529938b2c6298e19a2ec59 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 02:50:54 +0000 Subject: [PATCH 055/130] Updated markup for application edit form; image, radios --- lib/applicationeditform.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index f8fcb3e3f5..e9ab467804 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -175,7 +175,7 @@ class ApplicationEditForm extends Form $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'application_icon')); if (!empty($icon)) { $this->out->element('img', array('src' => $icon)); @@ -193,7 +193,7 @@ class ApplicationEditForm extends Form 'value' => ImageFile::maxFileSizeInt())); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li'); $this->out->hidden('application_id', $id); @@ -241,7 +241,7 @@ class ApplicationEditForm extends Form _('URL to redirect to after authentication')); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'application_types')); $attrs = array('name' => 'app_type', 'type' => 'radio', @@ -280,7 +280,7 @@ class ApplicationEditForm extends Form $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->elementStart('li', array('id' => 'default_access_types')); $attrs = array('name' => 'default_access_type', 'type' => 'radio', From 7694955cd654becf8f03dc4eca271d8188141596 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 01:16:42 +0000 Subject: [PATCH 056/130] Callback URL can be null --- db/statusnet.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/statusnet.sql b/db/statusnet.sql index c3160bcf87..b1867e7f04 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -218,7 +218,7 @@ create table oauth_application ( source_url varchar(255) comment 'application homepage - used for source link', organization varchar(255) comment 'name of the organization running the application', homepage varchar(255) comment 'homepage for the organization', - callback_url varchar(255) not null comment 'url to redirect to after authentication', + callback_url varchar(255) comment 'url to redirect to after authentication', type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', created datetime not null comment 'date this record was created', From adfca0180847571b9474db76a0c4daa407acf22b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 01:22:37 +0000 Subject: [PATCH 057/130] Can now edit/change application icon --- actions/editapplication.php | 124 ++++++++++++++++------------------ actions/newapplication.php | 81 +++++++--------------- classes/Oauth_application.php | 53 ++++++++++++--- 3 files changed, 126 insertions(+), 132 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index 6b8dd501c9..a0ed3117a7 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -93,47 +93,47 @@ class EditApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost($args); - } else { - $this->showForm(); - } + $this->handlePost($args); + } else { + $this->showForm(); + } } function handlePost($args) { - // Workaround for PHP returning empty $_POST and $_FILES when POST + // Workaround for PHP returning empty $_POST and $_FILES when POST // length > post_max_size in php.ini if (empty($_FILES) && empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0) - ) { + ) { $msg = _('The server was unable to handle that much POST ' . - 'data (%s bytes) due to its current configuration.'); + 'data (%s bytes) due to its current configuration.'); $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); return; } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } - $cur = common_current_user(); + $cur = common_current_user(); - if ($this->arg('cancel')) { - common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + if ($this->arg('cancel')) { + common_redirect(common_local_url('showapplication', + array( + 'nickname' => $cur->nickname, + 'id' => $this->app->id) + ), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -170,8 +170,8 @@ class EditApplicationAction extends OwnerDesignAction $access_type = $this->arg('default_access_type'); if (empty($name)) { - $this->showForm(_('Name is required.')); - return; + $this->showForm(_('Name is required.')); + return; } elseif (mb_strlen($name) > 255) { $this->showForm(_('Name is too long (max 255 chars).')); return; @@ -181,20 +181,17 @@ class EditApplicationAction extends OwnerDesignAction } elseif (Oauth_application::descriptionTooLong($description)) { $this->showForm(sprintf( _('Description is too long (max %d chars).'), - Oauth_application::maxDescription())); + Oauth_application::maxDescription())); return; - } elseif (empty($source_url)) { - $this->showForm(_('Source URL is required.')); - return; - } elseif ((strlen($source_url) > 0) - && !Validate::uri( - $source_url, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Source URL is not valid.')); + } elseif (mb_strlen($source_url) > 255) { + $this->showForm(_('Source URL is too long.')); return; + } elseif ((mb_strlen($source_url) > 0) + && !Validate::uri($source_url, + array('allowed_schemes' => array('http', 'https')))) + { + $this->showForm(_('Source URL is not valid.')); + return; } elseif (empty($organization)) { $this->showForm(_('Organization is required.')); return; @@ -204,35 +201,30 @@ class EditApplicationAction extends OwnerDesignAction } elseif (empty($homepage)) { $this->showForm(_('Organization homepage is required.')); return; - } elseif ((strlen($homepage) > 0) - && !Validate::uri( - $homepage, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Homepage is not a valid URL.')); - return; - } elseif (empty($callback_url)) { - $this->showForm(_('Callback is required.')); - return; - } elseif (strlen($callback_url) > 0 - && !Validate::uri( - $source_url, - array('allowed_schemes' => array('http', 'https')) - ) - ) - { - $this->showForm(_('Callback URL is not valid.')); - return; - } + } elseif ((mb_strlen($homepage) > 0) + && !Validate::uri($homepage, + array('allowed_schemes' => array('http', 'https')))) + { + $this->showForm(_('Homepage is not a valid URL.')); + return; + } elseif (mb_strlen($callback_url) > 255) { + $this->showForm(_('Callback is too long.')); + return; + } elseif (mb_strlen($callback_url) > 0 + && !Validate::uri($source_url, + array('allowed_schemes' => array('http', 'https')) + )) + { + $this->showForm(_('Callback URL is not valid.')); + return; + } $cur = common_current_user(); // Checked in prepare() above assert(!is_null($cur)); - assert(!is_null($this->app)); + assert(!is_null($this->app)); $orig = clone($this->app); @@ -244,9 +236,7 @@ class EditApplicationAction extends OwnerDesignAction $this->app->callback_url = $callback_url; $this->app->type = $type; - $result = $this->app->update($orig); - - common_debug("access_type = $access_type"); + common_debug("access_type = $access_type"); if ($access_type == 'r') { $this->app->access_type = 1; @@ -254,11 +244,15 @@ class EditApplicationAction extends OwnerDesignAction $this->app->access_type = 3; } + $result = $this->app->update($orig); + if (!$result) { common_log_db_error($this->app, 'UPDATE', __FILE__); $this->serverError(_('Could not update application.')); } + $this->app->uploadLogo(); + common_redirect(common_local_url('apps', array('nickname' => $cur->nickname)), 303); } diff --git a/actions/newapplication.php b/actions/newapplication.php index a0e61d288c..3d42b657be 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -83,7 +83,7 @@ class NewApplicationAction extends OwnerDesignAction parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost($args); + $this->handlePost($args); } else { $this->showForm(); } @@ -91,36 +91,36 @@ class NewApplicationAction extends OwnerDesignAction function handlePost($args) { - // Workaround for PHP returning empty $_POST and $_FILES when POST + // Workaround for PHP returning empty $_POST and $_FILES when POST // length > post_max_size in php.ini if (empty($_FILES) && empty($_POST) && ($_SERVER['CONTENT_LENGTH'] > 0) - ) { + ) { $msg = _('The server was unable to handle that much POST ' . - 'data (%s bytes) due to its current configuration.'); + 'data (%s bytes) due to its current configuration.'); $this->clientException(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); return; } - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->clientError(_('There was a problem with your session token.')); - return; - } + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.')); + return; + } - $cur = common_current_user(); + $cur = common_current_user(); - if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); - } elseif ($this->arg('save')) { - $this->trySave(); - } else { - $this->clientError(_('Unexpected form submission.')); - } + if ($this->arg('cancel')) { + common_redirect(common_local_url('apps', + array('nickname' => $cur->nickname)), 303); + } elseif ($this->arg('save')) { + $this->trySave(); + } else { + $this->clientError(_('Unexpected form submission.')); + } } function showForm($msg=null) @@ -147,7 +147,7 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); @@ -200,8 +200,8 @@ class NewApplicationAction extends OwnerDesignAction { $this->showForm(_('Homepage is not a valid URL.')); return; - } elseif (empty($callback_url)) { - $this->showForm(_('Callback is required.')); + } elseif (mb_strlen($callback_url) > 255) { + $this->showForm(_('Callback is too long.')); return; } elseif (strlen($callback_url) > 0 && !Validate::uri( @@ -266,7 +266,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('ROLLBACK'); } - $this->uploadLogo($app); + $this->app->uploadLogo(); $app->query('COMMIT'); @@ -275,40 +275,5 @@ class NewApplicationAction extends OwnerDesignAction } - /** - * Handle an image upload - * - * Does all the magic for handling an image upload, and crops the - * image by default. - * - * @return void - */ - - function uploadLogo($app) - { - if ($_FILES['app_icon']['error'] == - UPLOAD_ERR_OK) { - - try { - $imagefile = ImageFile::fromUpload('app_icon'); - } catch (Exception $e) { - common_debug("damn that sucks"); - $this->showForm($e->getMessage()); - return; - } - - $filename = Avatar::filename($app->id, - image_type_to_extension($imagefile->type), - null, - 'oauth-app-icon-'.common_timestamp()); - - $filepath = Avatar::path($filename); - - move_uploaded_file($imagefile->filepath, $filepath); - - $app->setOriginal($filename); - } - } - } diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php index 5df8b9459c..a6b5390872 100644 --- a/classes/Oauth_application.php +++ b/classes/Oauth_application.php @@ -27,7 +27,7 @@ class Oauth_application extends Memcached_DataObject /* Static get */ function staticGet($k,$v=NULL) { - return Memcached_DataObject::staticGet('Oauth_application',$k,$v); + return Memcached_DataObject::staticGet('Oauth_application',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE @@ -90,16 +90,51 @@ class Oauth_application extends Memcached_DataObject static function getByConsumerKey($key) { - if (empty($key)) { - return null; - } + if (empty($key)) { + return null; + } - $app = new Oauth_application(); - $app->consumer_key = $key; - $app->limit(1); - $result = $app->find(true); + $app = new Oauth_application(); + $app->consumer_key = $key; + $app->limit(1); + $result = $app->find(true); - return empty($result) ? null : $app; + return empty($result) ? null : $app; + } + + /** + * Handle an image upload + * + * Does all the magic for handling an image upload, and crops the + * image by default. + * + * @return void + */ + + function uploadLogo() + { + if ($_FILES['app_icon']['error'] == + UPLOAD_ERR_OK) { + + try { + $imagefile = ImageFile::fromUpload('app_icon'); + } catch (Exception $e) { + common_debug("damn that sucks"); + $this->showForm($e->getMessage()); + return; + } + + $filename = Avatar::filename($this->id, + image_type_to_extension($imagefile->type), + null, + 'oauth-app-icon-'.common_timestamp()); + + $filepath = Avatar::path($filename); + + move_uploaded_file($imagefile->filepath, $filepath); + + $this->setOriginal($filename); + } } } From 8da5e98cba12c32f0b75a90d1ff0007b73f0fc8d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 05:06:35 +0000 Subject: [PATCH 058/130] OAuth 1.0 working now --- actions/apioauthaccesstoken.php | 40 ++++----- actions/apioauthauthorize.php | 111 +++++++++++++++---------- actions/apioauthrequesttoken.php | 24 ++++-- lib/apiauth.php | 138 ++++++++++++++----------------- lib/apioauth.php | 122 +++++++++++++++++++++++++++ lib/apioauthstore.php | 69 +++++++++------- lib/router.php | 11 ++- 7 files changed, 330 insertions(+), 185 deletions(-) create mode 100644 lib/apioauth.php diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 67359d765d..085ef6f0b1 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Exchange an authorized OAuth request token for an access token @@ -43,19 +43,9 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthAccessTokenAction extends Action +class ApiOauthAccessTokenAction extends ApiOauthAction { - /** - * Is read only? - * - * @return boolean false - */ - function isReadOnly() - { - return false; - } - /** * Class handler. * @@ -73,7 +63,7 @@ class ApiOauthAccessTokenAction extends Action $server->add_signature_method($hmac_method); - $atok = null; + $atok = null; try { $req = OAuthRequest::from_request(); @@ -81,24 +71,24 @@ class ApiOauthAccessTokenAction extends Action } catch (OAuthException $e) { common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->outputError($e->getMessage()); - return; + common_debug(var_export($req, true)); + $this->outputError($e->getMessage()); + return; } - if (empty($atok)) { - common_debug('couldn\'t get access token.'); - print "Token exchange failed. Has the request token been authorized?\n"; - } else { - print $atok; - } + if (empty($atok)) { + common_debug('couldn\'t get access token.'); + print "Token exchange failed. Has the request token been authorized?\n"; + } else { + print $atok; + } } function outputError($msg) { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; } } diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 48d5087efc..cdf9cb7df3 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Authorize an OAuth request token @@ -43,7 +43,7 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthAuthorizeAction extends Action +class ApiOauthAuthorizeAction extends ApiOauthAction { var $oauth_token; var $callback; @@ -67,7 +67,7 @@ class ApiOauthAuthorizeAction extends Action { parent::prepare($args); - common_debug(var_export($_REQUEST, true)); + common_debug("apioauthauthorize"); $this->nickname = $this->trimmed('nickname'); $this->password = $this->arg('password'); @@ -130,7 +130,7 @@ class ApiOauthAuthorizeAction extends Action } else { - // XXX: make better error messages + // XXX: make better error messages if (empty($this->oauth_token)) { @@ -145,7 +145,8 @@ class ApiOauthAuthorizeAction extends Action return; } - common_debug("Requesting auth for app: $app->name."); + $name = $this->app->name; + common_debug("Requesting auth for app: " . $name); $this->showForm(); } @@ -153,6 +154,8 @@ class ApiOauthAuthorizeAction extends Action function handlePost() { + common_debug("handlePost()"); + // check session token for CSRF protection. $token = $this->trimmed('token'); @@ -170,7 +173,7 @@ class ApiOauthAuthorizeAction extends Action // check creds - $user = null; + $user = null; if (!common_logged_in()) { $user = common_check_user($this->nickname, $this->password); @@ -179,64 +182,86 @@ class ApiOauthAuthorizeAction extends Action return; } } else { - $user = common_current_user(); - } + $user = common_current_user(); + } if ($this->arg('allow')) { - // mark the req token as authorized + // mark the req token as authorized $this->store->authorize_token($this->oauth_token); - // Check to see if there was a previous token associated - // with this user/app and kill it. If you're doing this you - // probably don't want any old tokens anyway. + // Check to see if there was a previous token associated + // with this user/app and kill it. If the user is doing this she + // probably doesn't want any old tokens anyway. - $appUser = Oauth_application_user::getByKeys($user, $this->app); + $appUser = Oauth_application_user::getByKeys($user, $this->app); - if (!empty($appUser)) { - $result = $appUser->delete(); + if (!empty($appUser)) { + $result = $appUser->delete(); - if (!$result) { - common_log_db_error($appUser, 'DELETE', __FILE__); - throw new ServerException(_('DB error deleting OAuth app user.')); - return; - } - } + if (!$result) { + common_log_db_error($appUser, 'DELETE', __FILE__); + throw new ServerException(_('DB error deleting OAuth app user.')); + return; + } + } - // associated the new req token with the user and the app + // associated the authorized req token with the user and the app - $appUser = new Oauth_application_user(); + $appUser = new Oauth_application_user(); - $appUser->profile_id = $user->id; - $appUser->application_id = $this->app->id; - $appUser->access_type = $this->app->access_type; - $appUser->token = $this->oauth_token; - $appUser->created = common_sql_now(); + $appUser->profile_id = $user->id; + $appUser->application_id = $this->app->id; - $result = $appUser->insert(); + // Note: do not copy the access type from the application. + // The access type should always be 0 when the OAuth app + // user record has a request token associated with it. + // Access type gets assigned once an access token has been + // granted. The OAuth app user record then gets updated + // with the new access token and access type. - if (!$result) { - common_log_db_error($appUser, 'INSERT', __FILE__); - throw new ServerException(_('DB error inserting OAuth app user.')); - return; - } + $appUser->token = $this->oauth_token; + $appUser->created = common_sql_now(); + + $result = $appUser->insert(); + + if (!$result) { + common_log_db_error($appUser, 'INSERT', __FILE__); + throw new ServerException(_('DB error inserting OAuth app user.')); + return; + } // if we have a callback redirect and provide the token + // A callback specified in the app setup overrides whatever + // is passed in with the request. + + common_debug("Req token is authorized - doing callback"); + + if (!empty($this->app->callback_url)) { + $this->callback = $this->app->callback_url; + } + if (!empty($this->callback)) { - // XXX: Need better way to build this redirect url. + // XXX: Need better way to build this redirect url. + + $target_url = $this->getCallback($this->callback, + array('oauth_token' => $this->oauth_token)); + + common_debug("Doing callback to $target_url"); - $target_url = $this->callback . '?oauth_token=' . $this->oauth_token; common_redirect($target_url, 303); + } else { + common_debug("callback was empty!"); } // otherwise inform the user that the rt was authorized $this->elementStart('p'); - // XXX: Do OAuth 1.0a verifier code? + // XXX: Do OAuth 1.0a verifier code $this->raw(sprintf(_("The request token %s has been authorized. " . 'Please exchange it for an access token.'), @@ -267,9 +292,9 @@ class ApiOauthAuthorizeAction extends Action function showScripts() { parent::showScripts(); - if (!common_logged_in()) { - $this->autofocus('nickname'); - } + if (!common_logged_in()) { + $this->autofocus('nickname'); + } } /** @@ -313,9 +338,9 @@ class ApiOauthAuthorizeAction extends Action function showContent() { $this->elementStart('form', array('method' => 'post', - 'id' => 'form_login', - 'class' => 'form_settings', - 'action' => common_local_url('apioauthauthorize'))); + 'id' => 'form_login', + 'class' => 'form_settings', + 'action' => common_local_url('apioauthauthorize'))); $this->hidden('token', common_session_token()); $this->hidden('oauth_token', $this->oauth_token); diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 53aca6b96b..467640b9aa 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -31,7 +31,7 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Get an OAuth request token @@ -43,16 +43,28 @@ require_once INSTALLDIR . '/lib/apioauthstore.php'; * @link http://status.net/ */ -class ApiOauthRequestTokenAction extends Action +class ApiOauthRequestTokenAction extends ApiOauthAction { /** - * Is read only? + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag * - * @return boolean false */ - function isReadOnly() + + function prepare($args) { - return false; + parent::prepare($args); + + $this->callback = $this->arg('oauth_callback'); + + if (!empty($this->callback)) { + common_debug("callback: $this->callback"); + } + + return true; } /** diff --git a/lib/apiauth.php b/lib/apiauth.php index 3229ab19fd..431f3ac4fd 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -39,7 +39,7 @@ if (!defined('STATUSNET')) { } require_once INSTALLDIR . '/lib/api.php'; -require_once INSTALLDIR . '/lib/apioauthstore.php'; +require_once INSTALLDIR . '/lib/apioauth.php'; /** * Actions extending this class will require auth @@ -71,14 +71,14 @@ class ApiAuthAction extends ApiAction if ($this->requiresAuth()) { - $this->consumer_key = $this->arg('oauth_consumer_key'); - $this->access_token = $this->arg('oauth_token'); + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); - if (!empty($this->access_token)) { - $this->checkOAuthRequest(); - } else { - $this->checkBasicAuthUser(); - } + if (!empty($this->access_token)) { + $this->checkOAuthRequest(); + } else { + $this->checkBasicAuthUser(); + } } return true; @@ -86,101 +86,83 @@ class ApiAuthAction extends ApiAction function checkOAuthRequest() { - common_debug("We have an OAuth request."); + common_debug("We have an OAuth request."); - $datastore = new ApiStatusNetOAuthDataStore(); - $server = new OAuthServer($datastore); - $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + $datastore = new ApiStatusNetOAuthDataStore(); + $server = new OAuthServer($datastore); + $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); - $server->add_signature_method($hmac_method); + $server->add_signature_method($hmac_method); - $this->cleanRequest(); + ApiOauthAction::cleanRequest(); - try { + try { - $req = OAuthRequest::from_request(); - $server->verify_request($req); + $req = OAuthRequest::from_request(); + $server->verify_request($req); - common_debug("Good OAuth request!"); + common_debug("Good OAuth request!"); - $app = Oauth_application::getByConsumerKey($this->consumer_key); + $app = Oauth_application::getByConsumerKey($this->consumer_key); - if (empty($app)) { + if (empty($app)) { - // this should really not happen - common_log(LOG_WARN, - "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + // this should really not happen + common_log(LOG_WARN, + "Couldn't find the OAuth app for consumer key: $this->consumer_key"); - throw new OAuthException('No application for that consumer key.'); - } + throw new OAuthException('No application for that consumer key.'); + } - $appUser = Oauth_application_user::staticGet('token', - $this->access_token); + $appUser = Oauth_application_user::staticGet('token', + $this->access_token); - // XXX: check that app->id and appUser->application_id and consumer all - // match? + // XXX: check that app->id and appUser->application_id and consumer all + // match? - if (!empty($appUser)) { + if (!empty($appUser)) { - // read or read-write - $this->oauth_access_type = $appUser->access_type; + // read or read-write + $this->oauth_access_type = $appUser->access_type; - // If access_type == 0 we have either a request token - // or a bad / revoked access token + // If access_type == 0 we have either a request token + // or a bad / revoked access token - if ($this->oauth_access_type != 0) { + if ($this->oauth_access_type != 0) { - $this->auth_user = User::staticGet('id', $appUser->profile_id); + $this->auth_user = User::staticGet('id', $appUser->profile_id); - $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . - "application '%s' (id: %d)."; + $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . + "application '%s' (id: %d)."; - common_log(LOG_INFO, sprintf($msg, - $this->auth_user->nickname, - $this->auth_user->id, - $app->name, - $app->id)); - return true; - } else { - throw new OAuthException('Bad access token.'); - } - } else { + common_log(LOG_INFO, sprintf($msg, + $this->auth_user->nickname, + $this->auth_user->id, + $app->name, + $app->id)); + return true; + } else { + throw new OAuthException('Bad access token.'); + } + } else { - // also should not happen - throw new OAuthException('No user for that token.'); - } + // also should not happen + throw new OAuthException('No user for that token.'); + } - } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->showOAuthError($e->getMessage()); - exit(); - } + } catch (OAuthException $e) { + common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_debug(var_export($req, true)); + $this->showOAuthError($e->getMessage()); + exit(); + } } function showOAuthError($msg) { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; - } - - function cleanRequest() - { - // kill evil effects of magical slashing - - if(get_magic_quotes_gpc() == 1) { - $_POST = array_map('stripslashes', $_POST); - $_GET = array_map('stripslashes', $_GET); - } - - // strip out the p param added in index.php - - // XXX: should we strip anything else? Or alternatively - // only allow a known list of params? - - unset($_GET['p']); - unset($_POST['p']); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/html; charset=utf-8'); + print $msg . "\n"; } /** diff --git a/lib/apioauth.php b/lib/apioauth.php new file mode 100644 index 0000000000..4cb8a67754 --- /dev/null +++ b/lib/apioauth.php @@ -0,0 +1,122 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apioauthstore.php'; + +/** + * Base action for API OAuth enpoints. Clean up the + * the request, and possibly some other common things + * here. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiOauthAction extends Action +{ + /** + * Is this a read-only action? + * + * @return boolean false + */ + + function isReadOnly($args) + { + return false; + } + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle input, produce output + * + * Switches on request method; either shows the form or handles its input. + * + * @param array $args $_REQUEST data + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + self::cleanRequest(); + } + + static function cleanRequest() + { + // kill evil effects of magical slashing + + if (get_magic_quotes_gpc() == 1) { + $_POST = array_map('stripslashes', $_POST); + $_GET = array_map('stripslashes', $_GET); + } + + // strip out the p param added in index.php + + // XXX: should we strip anything else? Or alternatively + // only allow a known list of params? + + unset($_GET['p']); + unset($_POST['p']); + } + + function getCallback($url, $params) + { + foreach ($params as $k => $v) { + $url = $this->appendQueryVar($url, + OAuthUtil::urlencode_rfc3986($k), + OAuthUtil::urlencode_rfc3986($v)); + } + + return $url; + } + + function appendQueryVar($url, $k, $v) { + $url = preg_replace('/(.*)(\?|&)' . $k . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&'); + $url = substr($url, 0, -1); + if (strpos($url, '?') === false) { + return ($url . '?' . $k . '=' . $v); + } else { + return ($url . '&' . $k . '=' . $v); + } + } + +} diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index 290ce89730..c39ddbb0f3 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -40,44 +40,44 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); - $rt = new Token(); + $rt = new Token(); $rt->consumer_key = $consumer->key; $rt->tok = $token->key; $rt->type = 0; // request $app = Oauth_application::getByConsumerKey($consumer->key); - if (empty($app)) { - common_debug("empty app!"); - } + if (empty($app)) { + common_debug("empty app!"); + } - if ($rt->find(true) && $rt->state == 1) { // authorized + if ($rt->find(true) && $rt->state == 1) { // authorized common_debug('request token found.', __FILE__); - // find the associated user of the app + // find the associated user of the app - $appUser = new Oauth_application_user(); - $appUser->application_id = $app->id; - $appUser->token = $rt->tok; - $result = $appUser->find(true); + $appUser = new Oauth_application_user(); + $appUser->application_id = $app->id; + $appUser->token = $rt->tok; + $result = $appUser->find(true); - if (!empty($result)) { - common_debug("Oath app user found."); - } else { - common_debug("Oauth app user not found."); - return null; - } + if (!empty($result)) { + common_debug("Oath app user found."); + } else { + common_debug("Oauth app user not found."); + return null; + } - // go ahead and make the access token + // go ahead and make the access token - $at = new Token(); + $at = new Token(); $at->consumer_key = $consumer->key; $at->tok = common_good_rand(16); $at->secret = common_good_rand(16); $at->type = 1; // access $at->created = DB_DataObject_Cast::dateTime(); - if (!$at->insert()) { + if (!$at->insert()) { $e = $at->_lastError; common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__); return null; @@ -91,21 +91,30 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore } common_debug('request token "'.$rt->tok.'" updated', __FILE__); - // update the token from req to access for the user + // update the token from req to access for the user - $orig = clone($appUser); - $appUser->token = $at->tok; - $result = $appUser->update($orig); + $orig = clone($appUser); + $appUser->token = $at->tok; - if (empty($result)) { - common_debug('couldn\'t update OAuth app user.'); - return null; - } + // It's at this point that we change the access type + // to whatever the application's access is. Request + // tokens should always have an access type of 0, and + // therefore be unuseable for making requests for + // protected resources. - // Okay, good + $appUser->access_type = $app->access_type; - return new OAuthToken($at->tok, $at->secret); - } + $result = $appUser->update($orig); + + if (empty($result)) { + common_debug('couldn\'t update OAuth app user.'); + return null; + } + + // Okay, good + + return new OAuthToken($at->tok, $at->secret); + } } else { return null; diff --git a/lib/router.php b/lib/router.php index 420f5a0a10..d6e448c2f9 100644 --- a/lib/router.php +++ b/lib/router.php @@ -50,8 +50,7 @@ class Router var $m = null; static $inst = null; static $bare = array('requesttoken', 'accesstoken', 'userauthorization', - 'postnotice', 'updateprofile', 'finishremotesubscribe', - 'apioauthrequesttoken', 'apioauthaccesstoken'); + 'postnotice', 'updateprofile', 'finishremotesubscribe'); static function get() { @@ -659,7 +658,13 @@ class Router 'id' => '[0-9]+') ); - $m->connect('oauth/authorize', + $m->connect('api/oauth/request_token', + array('action' => 'apioauthrequesttoken')); + + $m->connect('api/oauth/access_token', + array('action' => 'apioauthaccesstoken')); + + $m->connect('api/oauth/authorize', array('action' => 'apioauthauthorize')); foreach (array('subscriptions', 'subscribers') as $a) { From 693b16174ad4142d1a543f78878c84c552ce6d74 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 05:31:48 +0000 Subject: [PATCH 059/130] Fix icon upload on new apps --- actions/newapplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/newapplication.php b/actions/newapplication.php index 3d42b657be..7bb81095dd 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -266,7 +266,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('ROLLBACK'); } - $this->app->uploadLogo(); + $app->uploadLogo(); $app->query('COMMIT'); From e101a6df6ba1cbec4664bb81fc81655e5db18b0f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 07:33:51 +0000 Subject: [PATCH 060/130] Rework application registration workflow to be more private --- actions/editapplication.php | 8 +-- actions/newapplication.php | 8 +-- actions/{apps.php => oauthappssettings.php} | 10 +--- actions/oauthconnectionssettings.php | 2 +- actions/showapplication.php | 25 +++------ lib/applicationeditform.php | 61 ++++++++++----------- lib/applicationlist.php | 13 ++--- lib/router.php | 23 +++----- 8 files changed, 58 insertions(+), 92 deletions(-) rename actions/{apps.php => oauthappssettings.php} (94%) diff --git a/actions/editapplication.php b/actions/editapplication.php index a0ed3117a7..a6db87c61e 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -125,10 +125,7 @@ class EditApplicationAction extends OwnerDesignAction if ($this->arg('cancel')) { common_redirect(common_local_url('showapplication', - array( - 'nickname' => $cur->nickname, - 'id' => $this->app->id) - ), 303); + array('id' => $this->app->id)), 303); } elseif ($this->arg('save')) { $this->trySave(); } else { @@ -253,8 +250,7 @@ class EditApplicationAction extends OwnerDesignAction $this->app->uploadLogo(); - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } } diff --git a/actions/newapplication.php b/actions/newapplication.php index 7bb81095dd..c499fe7c76 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -114,8 +114,7 @@ class NewApplicationAction extends OwnerDesignAction $cur = common_current_user(); if ($this->arg('cancel')) { - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } elseif ($this->arg('save')) { $this->trySave(); } else { @@ -147,7 +146,7 @@ class NewApplicationAction extends OwnerDesignAction function trySave() { - $name = $this->trimmed('name'); + $name = $this->trimmed('name'); $description = $this->trimmed('description'); $source_url = $this->trimmed('source_url'); $organization = $this->trimmed('organization'); @@ -270,8 +269,7 @@ class NewApplicationAction extends OwnerDesignAction $app->query('COMMIT'); - common_redirect(common_local_url('apps', - array('nickname' => $cur->nickname)), 303); + common_redirect(common_local_url('oauthappssettings'), 303); } diff --git a/actions/apps.php b/actions/oauthappssettings.php similarity index 94% rename from actions/apps.php rename to actions/oauthappssettings.php index 7c7b24570f..6c0670b17b 100644 --- a/actions/apps.php +++ b/actions/oauthappssettings.php @@ -46,7 +46,7 @@ require_once INSTALLDIR . '/lib/applicationlist.php'; * @see SettingsAction */ -class AppsAction extends SettingsAction +class OauthappssettingsAction extends SettingsAction { var $page = 0; @@ -116,10 +116,7 @@ class AppsAction extends SettingsAction $this->elementStart('p', array('id' => 'application_register')); $this->element('a', - array('href' => common_local_url( - 'newapplication', - array('nickname' => $user->nickname) - ), + array('href' => common_local_url('newapplication'), 'class' => 'more' ), 'Register a new application'); @@ -129,8 +126,7 @@ class AppsAction extends SettingsAction $this->page > 1, $cnt > APPS_PER_PAGE, $this->page, - 'apps', - array('nickname' => $user->nickname) + 'oauthappssettings' ); } diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 56e7b02fba..99bb9022b2 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -158,7 +158,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $this->elementStart('p'); $this->raw(_('Developers can edit the registration settings for their applications ')); $this->element('a', - array('href' => common_local_url('apps', array('nickname' => $cur->nickname))), + array('href' => common_local_url('oauthappssettings')), 'here.'); $this->elementEnd('p'); } diff --git a/actions/showapplication.php b/actions/showapplication.php index f2ff8b9002..bd33371368 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -211,15 +211,9 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('ul'); $this->elementStart('li', 'entity_edit'); $this->element('a', - array('href' => - common_local_url( - 'editapplication', - array( - 'nickname' => $this->owner->nickname, - 'id' => $this->application->id - ) - ) - ), 'Edit'); + array('href' => common_local_url('editapplication', + array('id' => $this->application->id))), + 'Edit'); $this->elementEnd('li'); $this->elementStart('li', 'entity_reset_keysecret'); @@ -228,8 +222,7 @@ class ShowApplicationAction extends OwnerDesignAction 'class' => 'form_reset_key', 'method' => 'POST', 'action' => common_local_url('showapplication', - array('nickname' => $cur->nickname, - 'id' => $this->application->id)))); + array('id' => $this->application->id)))); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); @@ -273,13 +266,9 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementStart('p', array('id' => 'application_action')); $this->element('a', - array( - 'href' => common_local_url( - 'apps', - array('nickname' => $this->owner->nickname)), - 'class' => 'more' - ), - 'View your applications'); + array('href' => common_local_url('oauthappssettings'), + 'class' => 'more'), + 'View your applications'); $this->elementEnd('p'); } diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index e9ab467804..040d3bf74b 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -119,12 +119,9 @@ class ApplicationEditForm extends Form if (!empty($this->application)) { return common_local_url('editapplication', - array('id' => $this->application->id, - 'nickname' => $cur->nickname) - ); + array('id' => $this->application->id)); } else { - return common_local_url('newapplication', - array('nickname' => $cur->nickname)); + return common_local_url('newapplication'); } } @@ -149,7 +146,7 @@ class ApplicationEditForm extends Form { if ($this->application) { $id = $this->application->id; - $icon = $this->application->icon; + $icon = $this->application->icon; $name = $this->application->name; $description = $this->application->description; $source_url = $this->application->source_url; @@ -160,7 +157,7 @@ class ApplicationEditForm extends Form $this->access_type = $this->application->access_type; } else { $id = ''; - $icon = ''; + $icon = ''; $name = ''; $description = ''; $source_url = ''; @@ -171,26 +168,26 @@ class ApplicationEditForm extends Form $this->access_type = ''; } - $this->out->hidden('token', common_session_token()); + $this->out->hidden('token', common_session_token()); $this->out->elementStart('ul', 'form_data'); - $this->out->elementStart('li', array('id' => 'application_icon')); + $this->out->elementStart('li', array('id' => 'application_icon')); - if (!empty($icon)) { - $this->out->element('img', array('src' => $icon)); - } + if (!empty($icon)) { + $this->out->element('img', array('src' => $icon)); + } - $this->out->element('label', array('for' => 'app_icon'), - _('Icon')); + $this->out->element('label', array('for' => 'app_icon'), + _('Icon')); $this->out->element('input', array('name' => 'app_icon', - 'type' => 'file', - 'id' => 'app_icon')); + 'type' => 'file', + 'id' => 'app_icon')); $this->out->element('p', 'form_guide', _('Icon for this application')); $this->out->element('input', array('name' => 'MAX_FILE_SIZE', - 'type' => 'hidden', - 'id' => 'MAX_FILE_SIZE', - 'value' => ImageFile::maxFileSizeInt())); + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); $this->out->elementEnd('li'); $this->out->elementStart('li'); @@ -207,13 +204,13 @@ class ApplicationEditForm extends Form $maxDesc = Oauth_application::maxDesc(); if ($maxDesc > 0) { $descInstr = sprintf(_('Describe your application in %d chars'), - $maxDesc); + $maxDesc); } else { $descInstr = _('Describe your application'); } $this->out->textarea('description', _('Description'), ($this->out->arg('description')) ? $this->out->arg('description') : $description, - $descInstr); + $descInstr); $this->out->elementEnd('li'); @@ -259,8 +256,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'app_type-browser', - 'class' => 'radio'), - _('Browser')); + 'class' => 'radio'), + _('Browser')); $attrs = array('name' => 'app_type', 'type' => 'radio', @@ -275,8 +272,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'app_type-desktop', - 'class' => 'radio'), - _('Desktop')); + 'class' => 'radio'), + _('Desktop')); $this->out->element('p', 'form_guide', _('Type of application, browser or desktop')); $this->out->elementEnd('li'); @@ -298,8 +295,8 @@ class ApplicationEditForm extends Form $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'default_access_type-ro', - 'class' => 'radio'), - _('Read-only')); + 'class' => 'radio'), + _('Read-only')); $attrs = array('name' => 'default_access_type', 'type' => 'radio', @@ -309,15 +306,15 @@ class ApplicationEditForm extends Form if ($this->application->access_type & Oauth_application::$readAccess && $this->application->access_type & Oauth_application::$writeAccess - ) { + ) { $attrs['checked'] = 'checked'; } $this->out->element('input', $attrs); $this->out->element('label', array('for' => 'default_access_type-rw', - 'class' => 'radio'), - _('Read-write')); + 'class' => 'radio'), + _('Read-write')); $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write')); $this->out->elementEnd('li'); @@ -334,8 +331,8 @@ class ApplicationEditForm extends Form function formActions() { $this->out->submit('cancel', _('Cancel'), 'submit form_action-primary', - 'cancel', _('Cancel')); + 'cancel', _('Cancel')); $this->out->submit('save', _('Save'), 'submit form_action-secondary', - 'save', _('Save')); + 'save', _('Save')); } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 15c2d588a3..f2eaefb401 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -64,7 +64,7 @@ class ApplicationList extends Widget $this->application = $application; $this->owner = $owner; $this->action = $action; - $this->connections = $connections; + $this->connections = $connections; } function show() @@ -97,10 +97,9 @@ class ApplicationList extends Widget $this->out->elementStart('span', 'vcard author'); if (!$this->connections) { $this->out->elementStart('a', - array('href' => common_local_url('showapplication', - array('nickname' => $user->nickname, - 'id' => $this->application->id)), - 'class' => 'url')); + array('href' => common_local_url('showapplication', + array('id' => $this->application->id)), + 'class' => 'url')); } else { $this->out->elementStart('a', array('href' => $this->application->source_url, @@ -154,8 +153,4 @@ class ApplicationList extends Widget return; } - function highlight($text) - { - return htmlspecialchars($text); - } } diff --git a/lib/router.php b/lib/router.php index d6e448c2f9..42bff27788 100644 --- a/lib/router.php +++ b/lib/router.php @@ -141,7 +141,7 @@ class Router // settings foreach (array('profile', 'avatar', 'password', 'im', 'oauthconnections', - 'email', 'sms', 'userdesign', 'other') as $s) { + 'oauthapps', 'email', 'sms', 'userdesign', 'other') as $s) { $m->connect('settings/'.$s, array('action' => $s.'settings')); } @@ -634,28 +634,23 @@ class Router // user stuff foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', 'apps', + 'nudge', 'all', 'foaf', 'xrds', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, array('action' => $a), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - $m->connect(':nickname/apps', - array('action' => 'apps'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/show/:id', + $m->connect('settings/oauthapps/show/:id', array('action' => 'showapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}', - 'id' => '[0-9]+') + array('id' => '[0-9]+') ); - $m->connect(':nickname/apps/new', - array('action' => 'newapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}')); - $m->connect(':nickname/apps/edit/:id', + $m->connect('settings/oauthapps/new', + array('action' => 'newapplication') + ); + $m->connect('settings/oauthapps/edit/:id', array('action' => 'editapplication'), - array('nickname' => '['.NICKNAME_FMT.']{1,64}', - 'id' => '[0-9]+') + array('id' => '[0-9]+') ); $m->connect('api/oauth/request_token', From c0eee277d1058c9c291b3c4474cc8a72cb8c6d0e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 11:31:15 +0000 Subject: [PATCH 061/130] Make sure applications are really looked up by consumer key --- actions/apioauthauthorize.php | 42 +++-------------------------------- lib/apioauthstore.php | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index cdf9cb7df3..0966ba1d71 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -74,42 +74,11 @@ class ApiOauthAuthorizeAction extends ApiOauthAction $this->oauth_token = $this->arg('oauth_token'); $this->callback = $this->arg('oauth_callback'); $this->store = new ApiStatusNetOAuthDataStore(); + $this->app = $this->store->getAppByRequestToken($this->oauth_token); return true; } - function getApp() - { - // Look up the full req token - - $req_token = $this->store->lookup_token(null, - 'request', - $this->oauth_token); - - if (empty($req_token)) { - - common_debug("Couldn't find request token!"); - - $this->clientError(_('Bad request.')); - return; - } - - // Look up the app - - $app = new Oauth_application(); - $app->consumer_key = $req_token->consumer_key; - $result = $app->find(true); - - if (!empty($result)) { - $this->app = $app; - return true; - - } else { - common_debug("couldn't find the app!"); - return false; - } - } - /** * Handle input, produce output * @@ -140,7 +109,8 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return; } - if (!$this->getApp()) { + if (empty($this->app)) { + common_debug('No app for that token.'); $this->clientError(_('Bad request.')); return; } @@ -166,11 +136,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return; } - if (!$this->getApp()) { - $this->clientError(_('Bad request.')); - return; - } - // check creds $user = null; @@ -416,7 +381,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction function getInstructions() { return _('Allow or deny access to your account information.'); - } /** diff --git a/lib/apioauthstore.php b/lib/apioauthstore.php index c39ddbb0f3..32110d0575 100644 --- a/lib/apioauthstore.php +++ b/lib/apioauthstore.php @@ -36,6 +36,44 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore $con->consumer_secret); } + function getAppByRequestToken($token_key) + { + // Look up the full req tokenx + + $req_token = $this->lookup_token(null, + 'request', + $token_key); + + if (empty($req_token)) { + common_debug("couldn't get request token from oauth datastore"); + return null; + } + + // Look up the full Token + + $token = new Token(); + $token->tok = $req_token->key; + $result = $token->find(true); + + if (empty($result)) { + common_debug('Couldn\'t find req token in the token table.'); + return null; + } + + // Look up the app + + $app = new Oauth_application(); + $app->consumer_key = $token->consumer_key; + $result = $app->find(true); + + if (!empty($result)) { + return $app; + } else { + common_debug("Couldn't find the app!"); + return null; + } + } + function new_access_token($token, $consumer) { common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__); @@ -64,7 +102,7 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore if (!empty($result)) { common_debug("Oath app user found."); } else { - common_debug("Oauth app user not found."); + common_debug("Oauth app user not found. app id $app->id token $rt->tok"); return null; } From ba68e042a8acb9dd1054e0bc1c5cd4dfd415642e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 17:52:25 +0000 Subject: [PATCH 062/130] Fix user count --- actions/showapplication.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index bd33371368..b21b994aa2 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -194,10 +194,13 @@ class ShowApplicationAction extends OwnerDesignAction $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; $profile = Profile::staticGet($this->application->owner); - $userCnt = 0; // XXX: count how many users use the app + + $appUsers = new Oauth_application_user(); + $appUsers->application_id = $this->application->id; + $userCnt = $appUsers->count(); $this->raw(sprintf( - _('Created by %1$s - %2$s access by default - %3$d users.'), + _('created by %1$s - %2$s access by default - %3$d users'), $profile->getBestName(), $defaultAccess, $userCnt @@ -222,7 +225,7 @@ class ShowApplicationAction extends OwnerDesignAction 'class' => 'form_reset_key', 'method' => 'POST', 'action' => common_local_url('showapplication', - array('id' => $this->application->id)))); + array('id' => $this->application->id)))); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); From 7b3c099f953c1569c18d81fdb8d4230e927d429e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 18:20:03 +0000 Subject: [PATCH 063/130] Ensure only the application's owner can edit it --- actions/editapplication.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/actions/editapplication.php b/actions/editapplication.php index a6db87c61e..9cc3e3cead 100644 --- a/actions/editapplication.php +++ b/actions/editapplication.php @@ -45,9 +45,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class EditApplicationAction extends OwnerDesignAction { - var $msg = null; - - var $app = null; + var $msg = null; + var $owner = null; + var $app = null; function title() { @@ -68,7 +68,14 @@ class EditApplicationAction extends OwnerDesignAction } $id = (int)$this->arg('id'); - $this->app = Oauth_application::staticGet($id); + + $this->app = Oauth_application::staticGet($id); + $this->owner = User::staticGet($this->app->owner); + $cur = common_current_user(); + + if ($cur->id != $this->owner->id) { + $this->clientError(_('You are not the owner of this application.'), 401); + } if (!$this->app) { $this->clientError(_('No such application.')); From cff2cfd7a7566542ce860410e7ef006a84869c7e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 18:33:13 +0000 Subject: [PATCH 064/130] Fix approval date and label on apps list --- lib/applicationlist.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/applicationlist.php b/lib/applicationlist.php index f2eaefb401..6eae261353 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -136,8 +136,8 @@ class ApplicationList extends Widget $access = ($this->application->access_type & Oauth_application::$writeAccess) ? 'read-write' : 'read-only'; - $txt = 'Approved ' . common_exact_date($appUser->modified) . - " $access for access."; + $txt = 'Approved ' . common_date_string($appUser->modified) . + " - $access access."; $this->out->raw($txt); $this->out->elementEnd('li'); From 6d58ef4abb12d735b6be777ea79f99a07c68694a Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 20:10:09 +0000 Subject: [PATCH 065/130] Updated apioauthauthorize markup and styles --- actions/apioauthauthorize.php | 46 +++++++++-------------------------- theme/base/css/display.css | 10 ++++++-- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 0966ba1d71..72d1426511 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -273,27 +273,6 @@ class ApiOauthAuthorizeAction extends ApiOauthAction return _('An application would like to connect to your account'); } - /** - * Show page notice - * - * Display a notice for how to use the page, or the - * error if it exists. - * - * @return void - */ - - function showPageNotice() - { - if ($this->error) { - $this->element('p', 'error', $this->error); - } else { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - - $this->raw($output); - } - } - /** * Shows the authorization form. * @@ -303,40 +282,38 @@ class ApiOauthAuthorizeAction extends ApiOauthAction function showContent() { $this->elementStart('form', array('method' => 'post', - 'id' => 'form_login', + 'id' => 'form_apioauthauthorize', 'class' => 'form_settings', 'action' => common_local_url('apioauthauthorize'))); + $this->elementStart('fieldset'); + $this->element('legend', array('id' => 'apioauthauthorize_allowdeny'), + _('Allow or deny access')); $this->hidden('token', common_session_token()); $this->hidden('oauth_token', $this->oauth_token); $this->hidden('oauth_callback', $this->callback); - $this->elementStart('fieldset'); - - $this->elementStart('ul'); + $this->elementStart('ul', 'form_data'); $this->elementStart('li'); + $this->elementStart('p'); if (!empty($this->app->icon)) { $this->element('img', array('src' => $this->app->icon)); } - $this->elementEnd('li'); - $this->elementStart('li'); $access = ($this->app->access_type & Oauth_application::$writeAccess) ? 'access and update' : 'access'; - $msg = _("The application %s by %s would like " . - "the ability to %s your account data."); + $msg = _("The application %s by %s would like " . + "the ability to %s your account data."); $this->raw(sprintf($msg, $this->app->name, $this->app->organization, $access)); - + $this->elementEnd('p'); $this->elementEnd('li'); $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - if (!common_logged_in()) { $this->elementStart('fieldset'); @@ -355,17 +332,18 @@ class ApiOauthAuthorizeAction extends ApiOauthAction } $this->element('input', array('id' => 'deny_submit', - 'class' => 'submit', + 'class' => 'submit submit form_action-primary', 'name' => 'deny', 'type' => 'submit', 'value' => _('Deny'))); $this->element('input', array('id' => 'allow_submit', - 'class' => 'submit', + 'class' => 'submit submit form_action-secondary', 'name' => 'allow', 'type' => 'submit', 'value' => _('Allow'))); + $this->elementEnd('fieldset'); $this->elementEnd('form'); } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 82670c964a..ff81d37273 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -177,7 +177,8 @@ font-weight:bold; #form_password_recover legend, #form_password_change legend, .form_entity_block legend, -#form_filter_bytag legend { +#form_filter_bytag legend, +#apioauthauthorize_allowdeny { display:none; } @@ -906,10 +907,15 @@ list-style-type:none; } .application img, #showapplication .entity_profile img, -#editapplication .form_data #application_icon img { +#editapplication .form_data #application_icon, +#apioauthauthorize .form_data img { max-width:96px; max-height:96px; } +#apioauthauthorize .form_data img { +margin-right:18px; +float:left; +} #showapplication .entity_profile { width:68%; } From dbcbc2fe7ff0368910a49ed5675b0edc1e2bf18d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 20:43:23 +0000 Subject: [PATCH 066/130] Changed legend text from Login to Account because it is not really logging iny --- actions/apioauthauthorize.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php index 72d1426511..fa074c4e76 100644 --- a/actions/apioauthauthorize.php +++ b/actions/apioauthauthorize.php @@ -317,7 +317,7 @@ class ApiOauthAuthorizeAction extends ApiOauthAction if (!common_logged_in()) { $this->elementStart('fieldset'); - $this->element('legend', null, _('Login')); + $this->element('legend', null, _('Account')); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); $this->input('nickname', _('Nickname')); From 9e7f47652d860d7f1e296dd369c4e68814bf2636 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:11:08 +0000 Subject: [PATCH 067/130] Revoke access token UI --- actions/oauthconnectionssettings.php | 62 ++++++++++++++++++++++++---- actions/showapplication.php | 1 + classes/Oauth_application_user.php | 2 +- classes/Profile.php | 3 +- lib/applicationlist.php | 14 ++++++- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 99bb9022b2..b17729b821 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -50,10 +50,12 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction { var $page = null; + var $id = null; function prepare($args) { parent::prepare($args); + $this->id = (int)$this->arg('id'); $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; return true; } @@ -101,16 +103,16 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction $application = $profile->getApplications($offset, $limit); - $cnt == 0; + $cnt == 0; - if (!empty($application)) { - $al = new ApplicationList($application, $user, $this, true); - $cnt = $al->show(); - } + if (!empty($application)) { + $al = new ApplicationList($application, $user, $this, true); + $cnt = $al->show(); + } - if ($cnt == 0) { - $this->showEmptyListMessage(); - } + if ($cnt == 0) { + $this->showEmptyListMessage(); + } $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE, $this->page, 'connectionssettings', @@ -139,6 +141,50 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction return; } + if ($this->arg('revoke')) { + $this->revokeAccess($this->id); + + // XXX: Show some indicator to the user of what's been done. + + $this->showPage(); + } else { + $this->clientError(_('Unexpected form submission.'), 401); + return false; + } + } + + function revokeAccess($appId) + { + $cur = common_current_user(); + + $app = Oauth_application::staticGet('id', $appId); + + if (empty($app)) { + $this->clientError(_('No such application.'), 404); + return false; + } + + $appUser = Oauth_application_user::getByKeys($cur, $app); + + if (empty($appUser)) { + $this->clientError(_('You are not a user of that application.'), 401); + return false; + } + + $orig = clone($appUser); + $appUser->access_type = 0; // No access + $result = $appUser->update(); + + if (!$result) { + common_log_db_error($orig, 'UPDATE', __FILE__); + $this->clientError(_('Unable to revoke access for app: ' . $app->id)); + return false; + } + + $msg = 'User %s (id: %d) revoked access to app %s (id: %d)'; + common_log(LOG_INFO, sprintf($msg, $cur->nickname, + $cur->id, $app->name, $app->id)); + } function showEmptyListMessage() diff --git a/actions/showapplication.php b/actions/showapplication.php index b21b994aa2..049206375d 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -92,6 +92,7 @@ class ShowApplicationAction extends OwnerDesignAction if ($cur->id != $this->owner->id) { $this->clientError(_('You are not the owner of this application.'), 401); + return false; } return true; diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index a05371f563..618d68133c 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -34,7 +34,7 @@ class Oauth_application_user extends Memcached_DataObject $oau = new Oauth_application_user(); $oau->profile_id = $user->id; - $oau->application_id = $app->id; + $oau->application_id = $app->id; $oau->limit(1); $result = $oau->find(true); diff --git a/classes/Profile.php b/classes/Profile.php index fef2a21710..1076fb2cb3 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -358,7 +358,8 @@ class Profile extends Memcached_DataObject 'SELECT a.* ' . 'FROM oauth_application_user u, oauth_application a ' . 'WHERE u.profile_id = %d ' . - 'AND a.id = u.application_id ' . + 'AND a.id = u.application_id ' . + 'AND u.access_type > 0 ' . 'ORDER BY u.created DESC '; if ($offset > 0) { diff --git a/lib/applicationlist.php b/lib/applicationlist.php index 6eae261353..3abb1f8aa7 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -142,7 +142,19 @@ class ApplicationList extends Widget $this->out->raw($txt); $this->out->elementEnd('li'); - // XXX: Add revoke access button + $this->out->elementStart('li', 'entity_revoke'); + $this->out->elementStart('form', array('id' => 'form_revoke_app', + 'class' => 'form_revoke_app', + 'method' => 'POST', + 'action' => + common_local_url('oauthconnectionssettings'))); + $this->out->elementStart('fieldset'); + $this->out->hidden('id', $this->application->id); + $this->out->hidden('token', common_session_token()); + $this->out->submit('revoke', _('Revoke')); + $this->out->elementEnd('fieldset'); + $this->out->elementEnd('form'); + $this->out->elementEnd('li'); } } From d33040089d660d897183df8e94c2a65bb8c8c85f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:14:22 +0000 Subject: [PATCH 068/130] Remove verifier from Oauth_application_user (not needed there) --- classes/Oauth_application_user.php | 1 - classes/statusnet.ini | 1 - db/statusnet.sql | 1 - 3 files changed, 3 deletions(-) diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php index 618d68133c..57986281f9 100644 --- a/classes/Oauth_application_user.php +++ b/classes/Oauth_application_user.php @@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject public $application_id; // int(4) primary_key not_null public $access_type; // tinyint(1) public $token; // varchar(255) - public $verifier; // varchar(255) public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index b358bbf60a..2b5f2c225b 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -373,7 +373,6 @@ profile_id = 129 application_id = 129 access_type = 17 token = 2 -verifier = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index b1867e7f04..3a74f9d7a0 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -230,7 +230,6 @@ create table oauth_application_user ( application_id integer not null comment 'id of the application' references oauth_application (id), access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', token varchar(255) comment 'request or access token', - verifier varchar(255) not null comment 'verification code', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', constraint primary key (profile_id, application_id) From 6efbf2777ac1ba934829a8e9ae381ca280621c0c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 21:31:19 +0000 Subject: [PATCH 069/130] Add verifier and verified callback to token for OAuth 1.0a --- classes/Token.php | 6 ++++-- classes/statusnet.ini | 2 ++ db/statusnet.sql | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/classes/Token.php b/classes/Token.php index 1fabd72f13..a129d1fd11 100644 --- a/classes/Token.php +++ b/classes/Token.php @@ -4,7 +4,7 @@ */ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; -class Token extends Memcached_DataObject +class Token extends Memcached_DataObject { ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -14,7 +14,9 @@ class Token extends Memcached_DataObject public $tok; // char(32) primary_key not_null public $secret; // char(32) not_null public $type; // tinyint(1) not_null - public $state; // tinyint(1) + public $state; // tinyint(1) + public $verifier; // varchar(255) + public $verified_callback; // varchar(255) public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 2b5f2c225b..6203650a69 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -516,6 +516,8 @@ tok = 130 secret = 130 type = 145 state = 17 +verifier = 2 +verified_callback = 2 created = 142 modified = 384 diff --git a/db/statusnet.sql b/db/statusnet.sql index 3a74f9d7a0..17de4fd0d4 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -189,6 +189,8 @@ create table token ( secret char(32) not null comment 'secret value', type tinyint not null default 0 comment 'request or access', state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used', + verifier varchar(255) comment 'verifier string for OAuth 1.0a', + verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified', From de70b91a3a42b07c86d3a0cd8868ded6510fd91c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 16:52:33 -0800 Subject: [PATCH 070/130] Some rough test scripts for poking at the OAuth system --- tests/oauth/README | 22 +++++++ tests/oauth/exchangetokens.php | 105 ++++++++++++++++++++++++++++++++ tests/oauth/getrequesttoken.php | 71 +++++++++++++++++++++ tests/oauth/oauth.ini | 10 +++ tests/oauth/verifycreds.php | 101 ++++++++++++++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 tests/oauth/README create mode 100755 tests/oauth/exchangetokens.php create mode 100755 tests/oauth/getrequesttoken.php create mode 100644 tests/oauth/oauth.ini create mode 100755 tests/oauth/verifycreds.php diff --git a/tests/oauth/README b/tests/oauth/README new file mode 100644 index 0000000000..ea4aabadbe --- /dev/null +++ b/tests/oauth/README @@ -0,0 +1,22 @@ +Some very rough test scripts for hitting up the OAuth endpoints. + +Note: this works best if you register an OAuth application, leaving +the callback URL blank. + +Put your instance info and consumer key and secret in oauth.ini + +Example usage: +-------------- + +php getrequesttoken.php + +Gets and request token, token secret and a url to authorize it. Once +you get the token/secret you can exchange it for an access token... + +php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657 + +Once you have your access token, go ahead and try an protected API +resource: + +php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8 + diff --git a/tests/oauth/exchangetokens.php b/tests/oauth/exchangetokens.php new file mode 100755 index 0000000000..2394826c7e --- /dev/null +++ b/tests/oauth/exchangetokens.php @@ -0,0 +1,105 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$ini = parse_ini_file("oauth.ini"); + +$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']); + +$at_endpoint = $ini['apiroot'] . $ini['access_token_url']; + +$shortoptions = 't:s:'; +$longoptions = array('oauth_token=', 'token_secret='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $rt); + +$r = httpRequest($req_req->to_url()); + +common_debug("Exchange request token = " . var_export($rt, true)); +common_debug("Exchange tokens URL: " . $req_req->to_url()); + +$body = $r->getBody(); + +$token_stuff = array(); +parse_str($body, $token_stuff); + +print 'Access token : ' . $token_stuff['oauth_token'] . "\n"; +print 'Access token secret : ' . $token_stuff['oauth_token_secret'] . "\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + diff --git a/tests/oauth/getrequesttoken.php b/tests/oauth/getrequesttoken.php new file mode 100755 index 0000000000..fc546a0f4c --- /dev/null +++ b/tests/oauth/getrequesttoken.php @@ -0,0 +1,71 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/scripts/commandline.inc'; +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$ini = parse_ini_file("oauth.ini"); + +$test_consumer = new OAuthConsumer($ini['consumer_key'], $ini['consumer_secret']); + +$rt_endpoint = $ini['apiroot'] . $ini['request_token_url']; + +$parsed = parse_url($rt_endpoint); +$params = array(); + +parse_str($parsed['query'], $params); + +$hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); + +$req_req = OAuthRequest::from_consumer_and_token($test_consumer, NULL, "GET", $rt_endpoint, $params); +$req_req->sign_request($hmac_method, $test_consumer, NULL); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +$token_stuff = array(); +parse_str($body, $token_stuff); + +$authurl = $ini['apiroot'] . $ini['authorize_url'] . '?oauth_token=' . $token_stuff['oauth_token']; + +print 'Request token : ' . $token_stuff['oauth_token'] . "\n"; +print 'Request token secret : ' . $token_stuff['oauth_token_secret'] . "\n"; +print "Authorize URL : $authurl\n"; + +//var_dump($req_req); + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + diff --git a/tests/oauth/oauth.ini b/tests/oauth/oauth.ini new file mode 100644 index 0000000000..5ef0e571eb --- /dev/null +++ b/tests/oauth/oauth.ini @@ -0,0 +1,10 @@ +; Setup OAuth info here +apiroot = "http://dev.controlyourself.ca/zach/api" + +request_token_url = "/oauth/request_token" +authorize_url = "/oauth/authorize" +access_token_url = "/oauth/access_token" + +consumer_key = "b748968e9bea81a53f3a3c15aa0c686f" +consumer_secret = "5434e18cce05d9e53cdd48029a62fa41" + diff --git a/tests/oauth/verifycreds.php b/tests/oauth/verifycreds.php new file mode 100755 index 0000000000..873bdb8bdd --- /dev/null +++ b/tests/oauth/verifycreds.php @@ -0,0 +1,101 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$shortoptions = 'o:s:'; +$longoptions = array('oauth_token=', 'token_secret='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $at); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +print "$body\n"; + +//print $req_req->to_url() . "\n\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->get($url); +} + From c2c930a8556cca866dfa0eba2fe1a8242eef71f2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 16:56:52 -0800 Subject: [PATCH 071/130] Fixed some spelling mistakes in the README --- tests/oauth/README | 6 +++--- tests/oauth/oauth.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/oauth/README b/tests/oauth/README index ea4aabadbe..dd76feb0c6 100644 --- a/tests/oauth/README +++ b/tests/oauth/README @@ -10,12 +10,12 @@ Example usage: php getrequesttoken.php -Gets and request token, token secret and a url to authorize it. Once -you get the token/secret you can exchange it for an access token... +Gets a request token, token secret and a url to authorize it. Once +you authorize the request token you can exchange it for an access token... php exchangetokens.php --oauth_token=b9a79548a88c1aa9a5bea73103c6d41d --token_secret=4a47d9337fc0202a14ab552e17a3b657 -Once you have your access token, go ahead and try an protected API +Once you have your access token, go ahead and try a protected API resource: php verifycreds.php --oauth_token=cf2de7665f0dda0a82c2dc39b01be7f9 --token_secret=4524c3b712200138e1a4cff2e9ca83d8 diff --git a/tests/oauth/oauth.ini b/tests/oauth/oauth.ini index 5ef0e571eb..16b747fe43 100644 --- a/tests/oauth/oauth.ini +++ b/tests/oauth/oauth.ini @@ -1,5 +1,5 @@ ; Setup OAuth info here -apiroot = "http://dev.controlyourself.ca/zach/api" +apiroot = "http://YOURSTATUSNET/api" request_token_url = "/oauth/request_token" authorize_url = "/oauth/authorize" From 1f8ddf716d0b54cc40aa89e595fe2232a10e7a2a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 14 Jan 2010 02:16:03 +0000 Subject: [PATCH 072/130] Check for read vs. read-write access on OAuth authenticated API mehtods. --- lib/api.php | 5 +++++ lib/apiauth.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/api.php b/lib/api.php index 707e4ac21a..794b140507 100644 --- a/lib/api.php +++ b/lib/api.php @@ -53,6 +53,9 @@ if (!defined('STATUSNET')) { class ApiAction extends Action { + const READ_ONLY = 1; + const READ_WRITE = 2; + var $format = null; var $user = null; var $auth_user = null; @@ -62,6 +65,8 @@ class ApiAction extends Action var $since_id = null; var $since = null; + var $access = self::READ_ONLY; // read (default) or read-write + /** * Initialization. * diff --git a/lib/apiauth.php b/lib/apiauth.php index 431f3ac4fd..8374c24a7f 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -78,12 +78,27 @@ class ApiAuthAction extends ApiAction $this->checkOAuthRequest(); } else { $this->checkBasicAuthUser(); + // By default, all basic auth users have read and write access + + $this->access = self::READ_WRITE; } } return true; } + function handle($args) + { + parent::handle($args); + + if ($this->isReadOnly($args) == false) { + if ($this->access == self::READ_ONLY) { + $this->clientError(_('API method requires write access.'), 401); + exit(); + } + } + } + function checkOAuthRequest() { common_debug("We have an OAuth request."); @@ -130,6 +145,10 @@ class ApiAuthAction extends ApiAction if ($this->oauth_access_type != 0) { + // Set the read or read-write access for the api call + $this->access = ($appUser->access_type & Oauth_application::$writeAccess) + ? self::READ_WRITE : self::READ_ONLY; + $this->auth_user = User::staticGet('id', $appUser->profile_id); $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . @@ -220,6 +239,7 @@ class ApiAuthAction extends ApiAction exit; } } + return true; } From 6a4c88afe723b246cf3a2158ac2c1ad4161bdd43 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 14 Jan 2010 02:32:59 +0000 Subject: [PATCH 073/130] More relaxed selector for application icon and form checkbox --- theme/base/css/display.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index ff81d37273..84e9426c77 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -907,7 +907,7 @@ list-style-type:none; } .application img, #showapplication .entity_profile img, -#editapplication .form_data #application_icon, +.form_data #application_icon img, #apioauthauthorize .form_data img { max-width:96px; max-height:96px; @@ -944,9 +944,9 @@ margin-left:1.795%; font-family:monospace; font-size:1.3em; } -#editapplication .form_data #application_types label.radio, -#editapplication .form_data #default_access_types label.radio { -width:15%; +.form_data #application_types label.radio, +.form_data #default_access_types label.radio { +width:14.5%; } /* NOTICE */ From 8b24b5ac7bea2098d3c85e342526c2102e2a6fb9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 20 Jan 2010 18:01:07 -0800 Subject: [PATCH 074/130] Add Start/EndSetApiUser events when setting API user via OAuth --- lib/apiauth.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index 8374c24a7f..f513ed2c9a 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -149,7 +149,10 @@ class ApiAuthAction extends ApiAction $this->access = ($appUser->access_type & Oauth_application::$writeAccess) ? self::READ_WRITE : self::READ_ONLY; - $this->auth_user = User::staticGet('id', $appUser->profile_id); + if (Event::handle('StartSetApiUser', array(&$user))) { + $this->auth_user = User::staticGet('id', $appUser->profile_id); + Event::handle('EndSetApiUser', array($user)); + } $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . "application '%s' (id: %d)."; From 4daf76212a6802863d20c6af7597eddded227ae8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 14 Jan 2010 02:38:01 +0000 Subject: [PATCH 075/130] - Had to remove checking read vs. read-write in OAuth authenticated methods - Will now pick up source attr from OAuth app --- actions/apiaccountverifycredentials.php | 14 ++++++++++++++ actions/apistatusesupdate.php | 5 +++++ lib/apiauth.php | 14 +++++--------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/actions/apiaccountverifycredentials.php b/actions/apiaccountverifycredentials.php index 08b201dbff..1095d51626 100644 --- a/actions/apiaccountverifycredentials.php +++ b/actions/apiaccountverifycredentials.php @@ -82,4 +82,18 @@ class ApiAccountVerifyCredentialsAction extends ApiAuthAction } + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + * + **/ + + function isReadOnly($args) + { + return true; + } + } diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index f594bbf393..f8bf7cf874 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -85,6 +85,11 @@ class ApiStatusesUpdateAction extends ApiAuthAction $this->lat = $this->trimmed('lat'); $this->lon = $this->trimmed('long'); + // try to set the source attr from OAuth app + if (empty($this->source)) { + $this->source = $this->oauth_source; + } + if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { $this->source = 'api'; } diff --git a/lib/apiauth.php b/lib/apiauth.php index f513ed2c9a..37070d212f 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -55,6 +55,7 @@ class ApiAuthAction extends ApiAction { var $access_token; var $oauth_access_type; + var $oauth_source; /** * Take arguments for running, and output basic auth header if needed @@ -90,13 +91,6 @@ class ApiAuthAction extends ApiAction function handle($args) { parent::handle($args); - - if ($this->isReadOnly($args) == false) { - if ($this->access == self::READ_ONLY) { - $this->clientError(_('API method requires write access.'), 401); - exit(); - } - } } function checkOAuthRequest() @@ -116,8 +110,6 @@ class ApiAuthAction extends ApiAction $req = OAuthRequest::from_request(); $server->verify_request($req); - common_debug("Good OAuth request!"); - $app = Oauth_application::getByConsumerKey($this->consumer_key); if (empty($app)) { @@ -129,6 +121,10 @@ class ApiAuthAction extends ApiAction throw new OAuthException('No application for that consumer key.'); } + // set the source attr + + $this->oauth_source = $app->name; + $appUser = Oauth_application_user::staticGet('token', $this->access_token); From e6cf293db8c465b21899fd328152a991501a5038 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:39:32 -0500 Subject: [PATCH 076/130] Recover caching logic lost in bad merge I made a bad merge on Jan 10th from master to 0.9.x. This lost a number of memcache enhancements made on the 0.9.x branch. I've been able to re-do the manual merge, and this represents the changes. Most of them are related to caching on insert. --- classes/Memcached_DataObject.php | 148 +++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 33645a3e8b..f59213c2cb 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -66,7 +66,6 @@ class Memcached_DataObject extends DB_DataObject // Clear this out so we don't accidentally break global // state in *this* process. $this->_DB_resultid = null; - // We don't have any local DBO refs, so clear these out. $this->_link_loaded = false; } @@ -91,9 +90,7 @@ class Memcached_DataObject extends DB_DataObject unset($i); } $i = Memcached_DataObject::getcached($cls, $k, $v); - if ($i) { - return $i; - } else { + if ($i === false) { // false == cache miss $i = DB_DataObject::factory($cls); if (empty($i)) { $i = false; @@ -101,22 +98,34 @@ class Memcached_DataObject extends DB_DataObject } $result = $i->get($k, $v); if ($result) { + // Hit! $i->encache(); - return $i; } else { + // save the fact that no such row exists + $c = self::memcache(); + if (!empty($c)) { + $ck = self::cachekey($cls, $k, $v); + $c->set($ck, null); + } $i = false; - return $i; } } + return $i; } - function &pkeyGet($cls, $kv) + /** + * @fixme Should this return false on lookup fail to match staticGet? + */ + function pkeyGet($cls, $kv) { $i = Memcached_DataObject::multicache($cls, $kv); - if ($i) { + if ($i !== false) { // false == cache miss return $i; } else { - $i = new $cls(); + $i = DB_DataObject::factory($cls); + if (empty($i)) { + return false; + } foreach ($kv as $k => $v) { $i->$k = $v; } @@ -124,6 +133,11 @@ class Memcached_DataObject extends DB_DataObject $i->encache(); } else { $i = null; + $c = self::memcache(); + if (!empty($c)) { + $ck = self::multicacheKey($cls, $kv); + $c->set($ck, null); + } } return $i; } @@ -132,6 +146,9 @@ class Memcached_DataObject extends DB_DataObject function insert() { $result = parent::insert(); + if ($result) { + $this->encache(); // in case of cached negative lookups + } return $result; } @@ -186,6 +203,17 @@ class Memcached_DataObject extends DB_DataObject function keyTypes() { + // ini-based classes return number-indexed arrays. handbuilt + // classes return column => keytype. Make this uniform. + + $keys = $this->keys(); + + $keyskeys = array_keys($keys); + + if (is_string($keyskeys[0])) { + return $keys; + } + global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { $this->databaseStructure(); @@ -197,6 +225,7 @@ class Memcached_DataObject extends DB_DataObject function encache() { $c = $this->memcache(); + if (!$c) { return false; } else if ($this->tableName() == 'user' && is_object($this->id)) { @@ -206,64 +235,86 @@ class Memcached_DataObject extends DB_DataObject str_replace("\n", " ", $e->getTraceAsString())); return false; } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this); - } - } - # XXX: should work for both compound and scalar pkeys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this); + $keys = $this->_allCacheKeys(); + + foreach ($keys as $key) { + $c->set($key, $this); + } } } function decache() { $c = $this->memcache(); + if (!$c) { return false; - } else { - $pkey = array(); - $pval = array(); - $types = $this->keyTypes(); - ksort($types); - foreach ($types as $key => $type) { - if ($type == 'K') { - $pkey[] = $key; - $pval[] = $this->$key; - } else { - $c->delete($this->cacheKey($this->tableName(), $key, $this->$key)); - } - } - # should work for both compound and scalar pkeys - # XXX: comma works for now but may not be safe separator for future keys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals)); } + + $keys = $this->_allCacheKeys(); + + foreach ($keys as $key) { + $c->delete($key, $this); + } + } + + function _allCacheKeys() + { + $ckeys = array(); + + $types = $this->keyTypes(); + ksort($types); + + $pkey = array(); + $pval = array(); + + foreach ($types as $key => $type) { + + assert(!empty($key)); + + if ($type == 'U') { + if (empty($this->$key)) { + continue; + } + $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key); + } else if ($type == 'K' || $type == 'N') { + $pkey[] = $key; + $pval[] = $this->$key; + } else { + throw new Exception("Unknown key type $key => $type for " . $this->tableName()); + } + } + + assert(count($pkey) > 0); + + // XXX: should work for both compound and scalar pkeys + $pvals = implode(',', $pval); + $pkeys = implode(',', $pkey); + + $ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals); + + return $ckeys; } function multicache($cls, $kv) { ksort($kv); - $c = Memcached_DataObject::memcache(); + $c = self::memcache(); if (!$c) { return false; } else { - $pkeys = implode(',', array_keys($kv)); - $pvals = implode(',', array_values($kv)); - return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); + return $c->get(self::multicacheKey($cls, $kv)); } } + static function multicacheKey($cls, $kv) + { + ksort($kv); + $pkeys = implode(',', array_keys($kv)); + $pvals = implode(',', array_values($kv)); + return self::cacheKey($cls, $pkeys, $pvals); + } + function getSearchEngine($table) { require_once INSTALLDIR.'/lib/search_engines.php'; @@ -298,7 +349,8 @@ class Memcached_DataObject extends DB_DataObject $key_part = common_keyize($cls).':'.md5($qry); $ckey = common_cache_key($key_part); $stored = $c->get($ckey); - if ($stored) { + + if ($stored !== false) { return new ArrayWrapper($stored); } From e400437d5783a5a1a03feb3ea3e5b6a40a71ed60 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:50:07 -0500 Subject: [PATCH 077/130] fix interpolation for positional arguments in showstream --- actions/showstream.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/showstream.php b/actions/showstream.php index 75e10858d0..90ff67073a 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -76,7 +76,7 @@ class ShowstreamAction extends ProfileAction if ($this->page == 1) { return $base; } else { - return sprintf(_("%1$s, page %2$d"), + return sprintf(_('%1$s, page %2$d'), $base, $this->page); } From fc7afed92478117d009d835a4d1cd0565ada1089 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:52:03 -0500 Subject: [PATCH 078/130] fix interpolation for positional arguments in replies --- actions/replies.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/replies.php b/actions/replies.php index 2e50f1c3c4..164c328db3 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -124,7 +124,7 @@ class RepliesAction extends OwnerDesignAction if ($this->page == 1) { return sprintf(_("Replies to %s"), $this->user->nickname); } else { - return sprintf(_("Replies to %1$s, page %2$d"), + return sprintf(_('Replies to %1$s, page %2$d'), $this->user->nickname, $this->page); } From 02526f1100621aa522ccb6beccc0a4e507da328a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:53:29 -0500 Subject: [PATCH 079/130] fix interpolation of positional arguments to sprintf in outbox --- actions/outbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/outbox.php b/actions/outbox.php index de30de0183..b81d4b9d0d 100644 --- a/actions/outbox.php +++ b/actions/outbox.php @@ -55,10 +55,10 @@ class OutboxAction extends MailboxAction function title() { if ($this->page > 1) { - return sprintf(_("Outbox for %1$s - page %2$d"), + return sprintf(_('Outbox for %1$s - page %2$d'), $this->user->nickname, $page); } else { - return sprintf(_("Outbox for %s"), $this->user->nickname); + return sprintf(_('Outbox for %s'), $this->user->nickname); } } From 9077db00a50a6123ece70eb690b658c19c5aeb27 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:54:25 -0500 Subject: [PATCH 080/130] fix interpolation of positional arguments to sprintf in inbox --- actions/inbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/inbox.php b/actions/inbox.php index f605cc9e8b..8330f753ff 100644 --- a/actions/inbox.php +++ b/actions/inbox.php @@ -56,10 +56,10 @@ class InboxAction extends MailboxAction function title() { if ($this->page > 1) { - return sprintf(_("Inbox for %1$s - page %2$d"), $this->user->nickname, + return sprintf(_('Inbox for %1$s - page %2$d'), $this->user->nickname, $this->page); } else { - return sprintf(_("Inbox for %s"), $this->user->nickname); + return sprintf(_('Inbox for %s'), $this->user->nickname); } } From 73fdec6c12c308732f0673f2d10ab1542c927a33 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:55:29 -0500 Subject: [PATCH 081/130] fix interpolation of positional arguments to sprintf in usergroups --- actions/usergroups.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/usergroups.php b/actions/usergroups.php index 5042261432..97faabae65 100644 --- a/actions/usergroups.php +++ b/actions/usergroups.php @@ -59,9 +59,9 @@ class UsergroupsAction extends OwnerDesignAction function title() { if ($this->page == 1) { - return sprintf(_("%s groups"), $this->user->nickname); + return sprintf(_('%s groups'), $this->user->nickname); } else { - return sprintf(_("%1$s groups, page %2$d"), + return sprintf(_('%1$s groups, page %2$d'), $this->user->nickname, $this->page); } From 019dad95e12191632a3828fb0950a80f305bbf01 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:56:41 -0500 Subject: [PATCH 082/130] fix interpolation of positional arguments to sprintf in show favorites --- actions/showfavorites.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/showfavorites.php b/actions/showfavorites.php index 6023f01567..f2d0822936 100644 --- a/actions/showfavorites.php +++ b/actions/showfavorites.php @@ -74,9 +74,9 @@ class ShowfavoritesAction extends OwnerDesignAction function title() { if ($this->page == 1) { - return sprintf(_("%s's favorite notices"), $this->user->nickname); + return sprintf(_('%s\'s favorite notices'), $this->user->nickname); } else { - return sprintf(_("%1$s's favorite notices, page %2$d"), + return sprintf(_('%1$s\'s favorite notices, page %2$d'), $this->user->nickname, $this->page); } From a9da43a41644b3e1ff27e8250fd858d2c2840e7b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:57:33 -0500 Subject: [PATCH 083/130] fix interpolation of positional arguments to sprintf in show group --- actions/showgroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/showgroup.php b/actions/showgroup.php index 06ae572e81..8042a49513 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -79,9 +79,9 @@ class ShowgroupAction extends GroupDesignAction } if ($this->page == 1) { - return sprintf(_("%s group"), $base); + return sprintf(_('%s group'), $base); } else { - return sprintf(_("%1$s group, page %2$d"), + return sprintf(_('%1$s group, page %2$d'), $base, $this->page); } From 089305ac7a89ba42286973feb14ca23687ec0995 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 24 Jan 2010 22:59:22 -0500 Subject: [PATCH 084/130] fix interpolation of positional arguments to sprintf in tag action --- actions/tag.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/tag.php b/actions/tag.php index 12857236ef..e91df6ea97 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -63,9 +63,9 @@ class TagAction extends Action function title() { if ($this->page == 1) { - return sprintf(_("Notices tagged with %s"), $this->tag); + return sprintf(_('Notices tagged with %s'), $this->tag); } else { - return sprintf(_("Notices tagged with %1$s, page %2$d"), + return sprintf(_('Notices tagged with %1$s, page %2$d'), $this->tag, $this->page); } @@ -85,7 +85,7 @@ class TagAction extends Action array('tag' => $this->tag)), sprintf(_('Notice feed for tag %s (RSS 1.0)'), $this->tag)), - new Feed(Feed::RSS2, + new Feed(Feed::RSS2, common_local_url('ApiTimelineTag', array('format' => 'rss', 'tag' => $this->tag)), From 73f6250b9d8a1f65bfcbf76b05f087bd6e118bea Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Mon, 25 Jan 2010 14:55:04 +0000 Subject: [PATCH 085/130] An update to geolocation cookie to use a single file and set the expiry date to 30 days from now. --- js/util.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/js/util.js b/js/util.js index a7339010a7..8d52d859b7 100644 --- a/js/util.js +++ b/js/util.js @@ -495,7 +495,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetDateFromNow(30) }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -537,7 +537,8 @@ var SN = { // StatusNet NLNU: location.url, NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); + + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetDateFromNow(30) }); }); } @@ -658,6 +659,13 @@ var SN = { // StatusNet } return false; }); + }, + + GetDateFromNow: function(days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + + return date; } }, From c10d5320dd5b308795c5a5bda2441fabfe278a84 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:07:24 -0800 Subject: [PATCH 086/130] Disable PubSubHubBub hub pings automatically on private site (hub wouldn't be able to read feeds anyway) [Might be good to think of a core way to mark a plugin as disabled when it initializes.] --- plugins/PubSubHubBub/PubSubHubBubPlugin.php | 44 ++++++++++++++++----- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php index ce6086df94..a880dc8666 100644 --- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php +++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php @@ -79,6 +79,21 @@ class PubSubHubBubPlugin extends Plugin parent::__construct(); } + /** + * Check if plugin should be active; may be mass-enabled. + * @return boolean + */ + + function enabled() + { + if (common_config('site', 'private')) { + // PuSH relies on public feeds + return false; + } + // @fixme check for being on a private network? + return true; + } + /** * Hooks the StartApiAtom event * @@ -92,8 +107,9 @@ class PubSubHubBubPlugin extends Plugin function onStartApiAtom($action) { - $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null); - + if ($this->enabled()) { + $action->element('link', array('rel' => 'hub', 'href' => $this->hub), null); + } return true; } @@ -110,9 +126,11 @@ class PubSubHubBubPlugin extends Plugin function onStartApiRss($action) { - $action->element('atom:link', array('rel' => 'hub', - 'href' => $this->hub), - null); + if ($this->enabled()) { + $action->element('atom:link', array('rel' => 'hub', + 'href' => $this->hub), + null); + } return true; } @@ -130,6 +148,9 @@ class PubSubHubBubPlugin extends Plugin function onHandleQueuedNotice($notice) { + if (!$this->enabled()) { + return false; + } $publisher = new Publisher($this->hub); $feeds = array(); @@ -243,16 +264,21 @@ class PubSubHubBubPlugin extends Plugin function onPluginVersion(&$versions) { + $about = _m('The PubSubHubBub plugin pushes RSS/Atom updates '. + 'to a PubSubHubBub hub.'); + if (!$this->enabled()) { + $about = '' . $about . ' ' . + _m('(inactive on private site)'); + } $versions[] = array('name' => 'PubSubHubBub', 'version' => STATUSNET_VERSION, 'author' => 'Craig Andrews', 'homepage' => 'http://status.net/wiki/Plugin:PubSubHubBub', 'rawdescription' => - _m('The PubSubHubBub plugin pushes RSS/Atom updates '. - 'to a PubSubHubBub hub.')); + $about); return true; } From 055a00bcaeaa50c36143bd151bf4433c5c87d174 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:36:20 -0800 Subject: [PATCH 087/130] drop now-unused --skip-xmpp and --xmpp-only options from queuedaemon.php --- scripts/queuedaemon.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index a9cfda6d72..bedd14b1a3 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -21,7 +21,7 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'fi:at:'; -$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only'); +$longoptions = array('id=', 'foreground', 'all', 'threads='); /** * Attempts to get a count of the processors available on the current system @@ -163,13 +163,6 @@ if (!$threads) { $daemonize = !(have_option('f') || have_option('--foreground')); $all = have_option('a') || have_option('--all'); -if (have_option('--skip-xmpp')) { - define('XMPP_EMERGENCY_FLAG', true); -} -if (have_option('--xmpp-only')) { - define('XMPP_ONLY_FLAG', true); -} - $daemon = new QueueDaemon($id, $daemonize, $threads, $all); $daemon->runOnce(); From ee4ca8f2601bc5a2dd258fea176037e03e23feb8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 09:41:40 -0800 Subject: [PATCH 088/130] quick fix to console.php: don't save blank lines into readline history --- scripts/console.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/console.php b/scripts/console.php index 8b62a3a967..4d207c261b 100755 --- a/scripts/console.php +++ b/scripts/console.php @@ -45,10 +45,12 @@ function read_input_line($prompt) if (CONSOLE_INTERACTIVE) { if (CONSOLE_READLINE) { $line = readline($prompt); - readline_add_history($line); - if (defined('CONSOLE_HISTORY')) { - // Save often; it's easy to hit fatal errors. - readline_write_history(CONSOLE_HISTORY); + if (trim($line) != '') { + readline_add_history($line); + if (defined('CONSOLE_HISTORY')) { + // Save often; it's easy to hit fatal errors. + readline_write_history(CONSOLE_HISTORY); + } } return $line; } else { From 1ab24832960eef698731cf0d3e72fccfa336392d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 25 Jan 2010 13:48:24 -0800 Subject: [PATCH 089/130] Fix presence notification on XMPP thread (now foreground, not background) --- lib/xmppmanager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index 299175dd7d..985e7c32e4 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -101,7 +101,7 @@ class XmppManager extends IoManager $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100); return !is_null($this->conn); } @@ -233,7 +233,7 @@ class XmppManager extends IoManager common_log(LOG_NOTICE, 'XMPP reconnected'); $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', -1); + $this->conn->presence(null, 'available', null, 'available', 100); } From d17b7fa19ba2bfec9aa3d734c0f30e001256ddc0 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 26 Jan 2010 01:58:10 +0100 Subject: [PATCH 090/130] Setting the geo location cookie expire date far into the future: 2029 ;) --- js/util.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/util.js b/js/util.js index 8d52d859b7..b864867fd1 100644 --- a/js/util.js +++ b/js/util.js @@ -495,7 +495,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetDateFromNow(30) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -538,7 +538,7 @@ var SN = { // StatusNet NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetDateFromNow(30) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); }); } @@ -661,9 +661,9 @@ var SN = { // StatusNet }); }, - GetDateFromNow: function(days) { + GetFullYear: function(year, month, day) { var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + date.setFullYear(year, month, day); return date; } From 655573c213113861cfd82a42ef999dde1aeea149 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 26 Jan 2010 00:21:05 -0500 Subject: [PATCH 091/130] Single-user mode New configuration options to define a single-user mode. This hides most of the "community" pages, like the public timeline and groups. The main user's timeline becomes the main page, and most other URLs are changed. Switching back and forth between 1-user and multi-user mode is probably hazardous. Squashed commit of the following: commit d814aa5c92d14a27a12baba7893f3f8bf63f1d08 Author: Evan Prodromou Date: Tue Jan 26 00:17:27 2010 -0500 don't show inbox and outbox in single-user mode commit 47f19b9523a7015d4c6e460b73ea32c839e00aa1 Author: Evan Prodromou Date: Tue Jan 26 00:15:22 2010 -0500 show correct URL for logo in single-user mode commit 552010cffc33eadbc512ec5a67619dbc2015239a Author: Evan Prodromou Date: Tue Jan 26 00:15:06 2010 -0500 make singleuser its own config section commit 786ab260a3ca172e57b555c75ca10946d8f258a1 Author: Evan Prodromou Date: Tue Jan 26 00:05:19 2010 -0500 make single-user mode work commit 5b21d7309b3a8dd5a4e0f29aea76f7897f1818b1 Author: Evan Prodromou Date: Mon Jan 25 23:45:55 2010 -0500 add single-user mode --- README | 9 ++ lib/action.php | 8 +- lib/default.php | 5 +- lib/personalgroupnav.php | 7 +- lib/router.php | 215 +++++++++++++++++++++++++-------------- 5 files changed, 161 insertions(+), 83 deletions(-) diff --git a/README b/README index 6022887899..f83873ca84 100644 --- a/README +++ b/README @@ -1492,6 +1492,15 @@ disabled: whether to enable this command. If enabled, users who send should enable it only after you've convinced yourself that it is safe. Default is 'false'. +singleuser +---------- + +If an installation has only one user, this can simplify a lot of the +interface. It also makes the user's profile the root URL. + +enabled: Whether to run in "single user mode". Default false. +nickname: nickname of the single user. + Plugins ======= diff --git a/lib/action.php b/lib/action.php index e242775585..3ffc452dec 100644 --- a/lib/action.php +++ b/lib/action.php @@ -392,8 +392,14 @@ class Action extends HTMLOutputter // lawsuit $this->elementStart('address', array('id' => 'site_contact', 'class' => 'vcard')); if (Event::handle('StartAddressData', array($this))) { + if (common_config('singleuser', 'enabled')) { + $url = common_local_url('showstream', + array('nickname' => common_config('singleuser', 'nickname'))); + } else { + $url = common_local_url('public'); + } $this->elementStart('a', array('class' => 'url home bookmark', - 'href' => common_local_url('public'))); + 'href' => $url)); if (common_config('site', 'logo') || file_exists(Theme::file('logo.png'))) { $this->element('img', array('class' => 'logo photo', 'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'), diff --git a/lib/default.php b/lib/default.php index b6ee72279d..35115542f2 100644 --- a/lib/default.php +++ b/lib/default.php @@ -56,7 +56,7 @@ $default = 'dupelimit' => 60, # default for same person saying the same thing 'textlimit' => 140, 'indent' => true, - 'use_x_sendfile' => false, + 'use_x_sendfile' => false ), 'db' => array('database' => 'YOU HAVE TO SET THIS IN config.php', @@ -260,4 +260,7 @@ $default = ), 'admin' => array('panels' => array('design', 'site', 'user', 'paths')), + 'singleuser' => + array('enabled' => false, + 'nickname' => null), ); diff --git a/lib/personalgroupnav.php b/lib/personalgroupnav.php index cdde1feca0..25db5baa92 100644 --- a/lib/personalgroupnav.php +++ b/lib/personalgroupnav.php @@ -78,9 +78,9 @@ class PersonalGroupNav extends Widget function show() { $user = null; - + // FIXME: we should probably pass this in - + $action = $this->action->trimmed('action'); $nickname = $this->action->trimmed('nickname'); @@ -117,7 +117,8 @@ class PersonalGroupNav extends Widget $cur = common_current_user(); - if ($cur && $cur->id == $user->id) { + if ($cur && $cur->id == $user->id && + !common_config('singleuser', 'enabled')) { $this->out->menuItem(common_local_url('inbox', array('nickname' => $nickname)), diff --git a/lib/router.php b/lib/router.php index 42bff27788..c0ddc8db38 100644 --- a/lib/router.php +++ b/lib/router.php @@ -73,12 +73,6 @@ class Router if (Event::handle('StartInitializeRouter', array(&$m))) { - // In the "root" - - $m->connect('', array('action' => 'public')); - $m->connect('rss', array('action' => 'publicrss')); - $m->connect('featuredrss', array('action' => 'featuredrss')); - $m->connect('favoritedrss', array('action' => 'favoritedrss')); $m->connect('opensearch/people', array('action' => 'opensearch', 'type' => 'people')); $m->connect('opensearch/notice', array('action' => 'opensearch', @@ -145,6 +139,18 @@ class Router $m->connect('settings/'.$s, array('action' => $s.'settings')); } + $m->connect('settings/oauthapps/show/:id', + array('action' => 'showapplication'), + array('id' => '[0-9]+') + ); + $m->connect('settings/oauthapps/new', + array('action' => 'newapplication') + ); + $m->connect('settings/oauthapps/edit/:id', + array('action' => 'editapplication'), + array('id' => '[0-9]+') + ); + // search foreach (array('group', 'people', 'notice') as $s) { @@ -227,11 +233,6 @@ class Router array('action' => 'peopletag'), array('tag' => '[a-zA-Z0-9]+')); - $m->connect('featured/', array('action' => 'featured')); - $m->connect('featured', array('action' => 'featured')); - $m->connect('favorited/', array('action' => 'favorited')); - $m->connect('favorited', array('action' => 'favorited')); - // groups $m->connect('group/new', array('action' => 'newgroup')); @@ -622,37 +623,6 @@ class Router $m->connect('api/search.json', array('action' => 'twitapisearchjson')); $m->connect('api/trends.json', array('action' => 'twitapitrends')); - $m->connect('admin/site', array('action' => 'siteadminpanel')); - $m->connect('admin/design', array('action' => 'designadminpanel')); - $m->connect('admin/user', array('action' => 'useradminpanel')); - $m->connect('admin/paths', array('action' => 'pathsadminpanel')); - - $m->connect('getfile/:filename', - array('action' => 'getfile'), - array('filename' => '[A-Za-z0-9._-]+')); - - // user stuff - - foreach (array('subscriptions', 'subscribers', - 'nudge', 'all', 'foaf', 'xrds', - 'replies', 'inbox', 'outbox', 'microsummary') as $a) { - $m->connect(':nickname/'.$a, - array('action' => $a), - array('nickname' => '[a-zA-Z0-9]{1,64}')); - } - - $m->connect('settings/oauthapps/show/:id', - array('action' => 'showapplication'), - array('id' => '[0-9]+') - ); - $m->connect('settings/oauthapps/new', - array('action' => 'newapplication') - ); - $m->connect('settings/oauthapps/edit/:id', - array('action' => 'editapplication'), - array('id' => '[0-9]+') - ); - $m->connect('api/oauth/request_token', array('action' => 'apioauthrequesttoken')); @@ -662,47 +632,136 @@ class Router $m->connect('api/oauth/authorize', array('action' => 'apioauthauthorize')); - foreach (array('subscriptions', 'subscribers') as $a) { - $m->connect(':nickname/'.$a.'/:tag', - array('action' => $a), - array('tag' => '[a-zA-Z0-9]+', + // Admin + + $m->connect('admin/site', array('action' => 'siteadminpanel')); + $m->connect('admin/design', array('action' => 'designadminpanel')); + $m->connect('admin/user', array('action' => 'useradminpanel')); + $m->connect('admin/paths', array('action' => 'pathsadminpanel')); + + $m->connect('getfile/:filename', + array('action' => 'getfile'), + array('filename' => '[A-Za-z0-9._-]+')); + + // In the "root" + + if (common_config('singleuser', 'enabled')) { + + $nickname = common_config('singleuser', 'nickname'); + + foreach (array('subscriptions', 'subscribers', + 'all', 'foaf', 'xrds', + 'replies', 'microsummary') as $a) { + $m->connect($a, + array('action' => $a, + 'nickname' => $nickname)); + } + + foreach (array('subscriptions', 'subscribers') as $a) { + $m->connect($a.'/:tag', + array('action' => $a, + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + } + + foreach (array('rss', 'groups') as $a) { + $m->connect($a, + array('action' => 'user'.$a, + 'nickname' => $nickname)); + } + + foreach (array('all', 'replies', 'favorites') as $a) { + $m->connect($a.'/rss', + array('action' => $a.'rss', + 'nickname' => $nickname)); + } + + $m->connect('favorites', + array('action' => 'showfavorites', + 'nickname' => $nickname)); + + $m->connect('avatar/:size', + array('action' => 'avatarbynickname', + 'nickname' => $nickname), + array('size' => '(original|96|48|24)')); + + $m->connect('tag/:tag/rss', + array('action' => 'userrss', + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('tag/:tag', + array('action' => 'showstream', + 'nickname' => $nickname), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect('', + array('action' => 'showstream', + 'nickname' => $nickname)); + + } else { + + $m->connect('', array('action' => 'public')); + $m->connect('rss', array('action' => 'publicrss')); + $m->connect('featuredrss', array('action' => 'featuredrss')); + $m->connect('favoritedrss', array('action' => 'favoritedrss')); + $m->connect('featured/', array('action' => 'featured')); + $m->connect('featured', array('action' => 'featured')); + $m->connect('favorited/', array('action' => 'favorited')); + $m->connect('favorited', array('action' => 'favorited')); + + foreach (array('subscriptions', 'subscribers', + 'nudge', 'all', 'foaf', 'xrds', + 'replies', 'inbox', 'outbox', 'microsummary') as $a) { + $m->connect(':nickname/'.$a, + array('action' => $a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('subscriptions', 'subscribers') as $a) { + $m->connect(':nickname/'.$a.'/:tag', + array('action' => $a), + array('tag' => '[a-zA-Z0-9]+', + 'nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('rss', 'groups') as $a) { + $m->connect(':nickname/'.$a, + array('action' => 'user'.$a), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + foreach (array('all', 'replies', 'favorites') as $a) { + $m->connect(':nickname/'.$a.'/rss', + array('action' => $a.'rss'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + } + + $m->connect(':nickname/favorites', + array('action' => 'showfavorites'), + array('nickname' => '[a-zA-Z0-9]{1,64}')); + + $m->connect(':nickname/avatar/:size', + array('action' => 'avatarbynickname'), + array('size' => '(original|96|48|24)', 'nickname' => '[a-zA-Z0-9]{1,64}')); - } - foreach (array('rss', 'groups') as $a) { - $m->connect(':nickname/'.$a, - array('action' => 'user'.$a), + $m->connect(':nickname/tag/:tag/rss', + array('action' => 'userrss'), + array('nickname' => '[a-zA-Z0-9]{1,64}'), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect(':nickname/tag/:tag', + array('action' => 'showstream'), + array('nickname' => '[a-zA-Z0-9]{1,64}'), + array('tag' => '[a-zA-Z0-9]+')); + + $m->connect(':nickname', + array('action' => 'showstream'), array('nickname' => '[a-zA-Z0-9]{1,64}')); } - foreach (array('all', 'replies', 'favorites') as $a) { - $m->connect(':nickname/'.$a.'/rss', - array('action' => $a.'rss'), - array('nickname' => '[a-zA-Z0-9]{1,64}')); - } - - $m->connect(':nickname/favorites', - array('action' => 'showfavorites'), - array('nickname' => '[a-zA-Z0-9]{1,64}')); - - $m->connect(':nickname/avatar/:size', - array('action' => 'avatarbynickname'), - array('size' => '(original|96|48|24)', - 'nickname' => '[a-zA-Z0-9]{1,64}')); - - $m->connect(':nickname/tag/:tag/rss', - array('action' => 'userrss'), - array('nickname' => '[a-zA-Z0-9]{1,64}'), - array('tag' => '[a-zA-Z0-9]+')); - - $m->connect(':nickname/tag/:tag', - array('action' => 'showstream'), - array('nickname' => '[a-zA-Z0-9]{1,64}'), - array('tag' => '[a-zA-Z0-9]+')); - - $m->connect(':nickname', - array('action' => 'showstream'), - array('nickname' => '[a-zA-Z0-9]{1,64}')); + // user stuff Event::handle('RouterInitialized', array($m)); } From 7fc5588c5dfc0a74cbc1c791bc445e399207c443 Mon Sep 17 00:00:00 2001 From: Julien C Date: Tue, 8 Dec 2009 22:16:03 +0100 Subject: [PATCH 092/130] Allow logging in using Twitter Signed-off-by: Julien C --- plugins/TwitterBridge/TwitterBridgePlugin.php | 26 ++ plugins/TwitterBridge/twitter_connect.gif | Bin 0 -> 2205 bytes .../TwitterBridge/twitterauthorization.php | 431 ++++++++++++++++-- plugins/TwitterBridge/twitterlogin.php | 95 ++++ 4 files changed, 526 insertions(+), 26 deletions(-) create mode 100644 plugins/TwitterBridge/twitter_connect.gif create mode 100644 plugins/TwitterBridge/twitterlogin.php diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 57b3c1c995..e39ec7be03 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -72,9 +72,34 @@ class TwitterBridgePlugin extends Plugin $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); + + $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); return true; } + + + + /* + * Add a login tab for Twitter Connect + * + * @param Action &action the current action + * + * @return void + */ + function onEndLoginGroupNav(&$action) + { + + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('twitterlogin'), + _('Twitter'), + _('Login or register using Twitter'), + 'twitterlogin' === $action_name); + + return true; + } + /** * Add the Twitter Settings page to the Connect Settings menu @@ -108,6 +133,7 @@ class TwitterBridgePlugin extends Plugin switch ($cls) { case 'TwittersettingsAction': case 'TwitterauthorizationAction': + case 'TwitterloginAction': include_once INSTALLDIR . '/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; diff --git a/plugins/TwitterBridge/twitter_connect.gif b/plugins/TwitterBridge/twitter_connect.gif new file mode 100644 index 0000000000000000000000000000000000000000..e3d8f3ed7b88b41d67b37f8be92f7af532f25816 GIT binary patch literal 2205 zcmV;O2x9j~Nk%w1VZ#6!0OkMyGOg67-0df;(tOI~iqPgGrqG4s`T%UCxby!1|NoiR z>U7=s1AMdT`u<+N-BG#QRK4EB^Zq5M(rv}y7M#u^rO(Ca^Et8E-TMFJ`~FDL^d_m& z)9v;kq|V9r{#DfX;`8}Eve_!A)uZeE@&5l)!QoB5-kj|Hw&L)u@BPyF{urLnAE(ej zver1x^fa&6@cH}_k-@g%?_j{*LbTXLwcN7q{)x}#PPo~V>HH9lzii$4dEogls@Ef@ z(E0rS-uM5??Dvb&=MRp+5RbqptJ5f}(jTPHD6G>UrqCXx&m^ePDXh~Wr_djz&`rMG z1ADR_rq3s=(oVnL0BfTlrO+Rx&mN@CO1Rt;na)PI-NfheLbBN*rqCj!(?ho0A)wG- zz~3pS)Fi0Y8lTW0q|+p;(Lb@)GqKb?wcH||$@%#F&h7Z^_W4e?*)_7;!|(k;&-9nt z@g}IzF09r>x7hLd{3WB(_W1j|?EUrq{-*H$)babK<@2rL@&Et+A^8LV00000EC2ui z0K)(o000O7fKX5jgM|!-g@cESjEIDaj*Ehmh>(_+jgx|ejhv5>mywd2pP!1Dq@s+Q zn4XZ5kEEKZu&<7Zhf`BeySu!;Pr<)WCcbLA$h^P0!ph9Ty~xSW!N9}P)X&k?%e~#e zy(ZVg&d}b?;>^0m=DySC&C$ityX@B6hekz6MNCHgNB{;D$b<>N7)A;zT*$DY!-o(f zN}SlRh{cN-Giuz(F{6=>AVZ2ANwVa~B1QyZS;?}c%a(bL!m5v!~CW zKzZ66N)+V2Lt2v7NXoRS)220Ibeu}Hs@1DiDQexy5UW>?2?x~}OIFU0KWre7UCTCx z!Ju&e;~sqrU)3KbX(}ykl+JX3MK>yFa*#gfF+{vfEi|>fkFZ> zC~)2cbHXsddI?x?o|b-2kU#(%AV{cSVesjvaVQvP!2=RRDnW$z zDSJXF8mR(eqVPhY$64TKiw{b0NC+>)`ods)Xo$eAx1Nx}8r8`f!xHZWkbs@;m1qH` zFsvvT2ttS%pmBA!X&wi4&KaPfg9;IbmV*v(-V3E|fGG$Y2s^M*}&ekirQRQ)~eL26!~g zFcCg@c_n}z3kt%O!UnK}U=u7c@Dj!;umrX*C+L94K9op+4>40Eg(g}cxAkfg~5}hvbbkk)by~NZYIN$)jG!#sx$7rXmwwE0r zqA?u_WRSxMBiLYr#3LXuC>#0v%LmFC;$M&B!e3SU;-7m!M|<*Lws#v z02Rn#4q*TT5LAGE2{=OzcUA!#;t+i|sKC~ASb;PA%K;T|1Dyg;0WtY7h6&h06Q5Yb zEDoRw=;Oc*6p%z2YLSU(Orsjts6#?{;f-GCK^s)Cg9qy22X-Jq0|Zk7IKEK_1?Ykx z2ib)r3;=yw8bA`j#6lbha%WwDUl&Z6gslk@2Qm;72Vhu*0hCDpPZgMez0g=PEOJ1H zL=@$q&X+#-v5x@w8i4s?SpfPe;C)$qA1zfG02KxRUmz%B7THM5V(PF9gBSxG0bmCV zeBgl#;K2eGumc_lW(>-FBN39&gl>BCo8SzmILArOa+>p;-AsZy*U8Ryy7QgzjHf!$ zNzZetlMrG6gFf#Ngamxx1Ja!4G+i))1c)J@`Wylaww79YPYEK+~Gq^rkq?sZMEHg`WEK zr$7y=P=`uXpYHUiNKL9zmx|OX9N`cmT|r30Aeaci!KxAZ$ODx6AW|}{z=B5zt60ZM z*0P%QtY}TETGz_fwz_q!#*C|6b!deS41xl9%_{}ozz4^bMh;%p>kumN*2123u82*n zViRlF#*(!HRyc%SCrjDNQeX&5$gE~J%h}F)_OqH5s})8|+R~c#w5Uz3YNNo~*1Gn! zu#K&3S$l=r+V-}%&8=>Cn_DGB;SgB}u5gD-+~Njz2v-2Da+k~8<~sMe(2cHVXG`7c zS~m(xIK% + * @author Zach Copley * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -50,6 +50,10 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; */ class TwitterauthorizationAction extends Action { + var $twuid = null; + var $tw_fields = null; + var $access_token = null; + /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -76,29 +80,56 @@ class TwitterauthorizationAction extends Action function handle($args) { parent::handle($args); - - if (!common_logged_in()) { - $this->clientError(_m('Not logged in.'), 403); + + if (common_logged_in()) { + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + + // If there's already a foreign link record, it means we already + // have an access token, and this is unecessary. So go back. + + if (isset($flink)) { + common_redirect(common_local_url('twittersettings')); + } } + + + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // User was not logged in to StatusNet before + $this->twuid = $this->trimmed('twuid'); + $this->tw_fields = array("name" => $this->trimmed('name'), "fullname" => $this->trimmed('fullname')); + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - - // If there's already a foreign link record, it means we already - // have an access token, and this is unecessary. So go back. - - if (isset($flink)) { - common_redirect(common_local_url('twittersettings')); - } - - // $this->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process - - if (empty($this->oauth_token)) { - $this->authorizeRequestToken(); + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. Try again, please.')); + return; + } + if ($this->arg('create')) { + if (!$this->boolean('license')) { + $this->showForm(_('You can\'t register if you don\'t agree to the license.'), + $this->trimmed('newname')); + return; + } + $this->createNewUser(); + } else if ($this->arg('connect')) { + $this->connectNewUser(); + } else { + common_debug('Twitter Connect Plugin - ' . + print_r($this->args, true)); + $this->showForm(_('Something weird happened.'), + $this->trimmed('newname')); + } } else { - $this->saveAccessToken(); + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + + if (empty($this->oauth_token)) { + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } } } @@ -170,16 +201,25 @@ class TwitterauthorizationAction extends Action $this->serverError(_m('Couldn\'t link your Twitter account.')); } - // Save the access token and Twitter user info - - $this->saveForeignLink($atok, $twitter_user); + if (common_logged_in()) { + // Save the access token and Twitter user info + $this->saveForeignLink($atok, $twitter_user); + } + else{ + $this->twuid = $twitter_user->id; + $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); + $this->access_token = $atok; + $this->tryLogin(); + } // Clean up the the mess we made in the session unset($_SESSION['twitter_request_token']); unset($_SESSION['twitter_request_token_secret']); - - common_redirect(common_local_url('twittersettings')); + + if (common_logged_in()) { + common_redirect(common_local_url('twittersettings')); + } } /** @@ -220,5 +260,344 @@ class TwitterauthorizationAction extends Action save_twitter_user($twitter_user->id, $twitter_user->screen_name); } + + + + function showPageNotice() + { + if ($this->error) { + $this->element('div', array('class' => 'error'), $this->error); + } else { + $this->element('div', 'instructions', + sprintf(_('This is the first time you\'ve logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); + } + } + + function title() + { + return _('Twitter Account Setup'); + } + + function showForm($error=null, $username=null) + { + $this->error = $error; + $this->username = $username; + + $this->showPage(); + } + + function showPage() + { + parent::showPage(); + } + + function showContent() + { + if (!empty($this->message_text)) { + $this->element('p', null, $this->message); + return; + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_twitter_connect', + 'class' => 'form_settings', + 'action' => common_local_url('twitterauthorization'))); + $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options')); + $this->element('legend', null, _('Connection options')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->element('input', array('type' => 'checkbox', + 'id' => 'license', + 'class' => 'checkbox', + 'name' => 'license', + 'value' => 'true')); + $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); + $this->text(_('My text and files are available under ')); + $this->element('a', array('href' => common_config('license', 'url')), + common_config('license', 'title')); + $this->text(_(' except this private data: password, email address, IM address, phone number.')); + $this->elementEnd('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->hidden('access_token_key', $this->access_token->key); + $this->hidden('access_token_secret', $this->access_token->secret); + $this->hidden('twuid', $this->twuid); + $this->hidden('tw_fields_name', $this->tw_fields['name']); + $this->hidden('tw_fields_fullname', $this->tw_fields['fullname']); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, + _('Create new account')); + $this->element('p', null, + _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('newname', _('New nickname'), + ($this->username) ? $this->username : '', + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('create', _('Create')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + $this->element('legend', null, + _('Connect existing account')); + $this->element('p', null, + _('If you already have an account, login with your username and password to connect it to your Twitter account.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); + + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function message($msg) + { + $this->message_text = $msg; + $this->showPage(); + } + + function createNewUser() + { + if (common_config('site', 'closed')) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = null; + + if (common_config('site', 'inviteonly')) { + $code = $_SESSION['invitecode']; + if (empty($code)) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = Invitation::staticGet($code); + + if (empty($invite)) { + $this->clientError(_('Not a valid invitation code.')); + return; + } + } + + $nickname = $this->trimmed('newname'); + + if (!Validate::string($nickname, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); + return; + } + + if (!User::allowed_nickname($nickname)) { + $this->showForm(_('Nickname not allowed.')); + return; + } + + if (User::staticGet('nickname', $nickname)) { + $this->showForm(_('Nickname already in use. Try another one.')); + return; + } + + $fullname = trim($this->tw_fields['fullname']); + + $args = array('nickname' => $nickname, 'fullname' => $fullname); + + if (!empty($invite)) { + $args['code'] = $invite->code; + } + + $user = User::register($args); + + $result = $this->flinkUser($user->id, $this->twuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_set_user($user); + common_real_login(true); + + common_debug('Twitter Connect Plugin - ' . + "Registered new user $user->id from Twitter user $this->fbuid"); + + common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), + 303); + } + + function connectNewUser() + { + $nickname = $this->trimmed('nickname'); + $password = $this->trimmed('password'); + + if (!common_check_user($nickname, $password)) { + $this->showForm(_('Invalid username or password.')); + return; + } + + $user = User::staticGet('nickname', $nickname); + + if (!empty($user)) { + common_debug('Twitter Connect Plugin - ' . + "Legit user to connect to Twitter: $nickname"); + } + + $result = $this->flinkUser($user->id, $this->twuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_debug('Twitter Connnect Plugin - ' . + "Connected Twitter user $this->fbuid to local user $user->id"); + + common_set_user($user); + common_real_login(true); + + $this->goHome($user->nickname); + } + + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->twuid); + + if (empty($result)) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_debug('Twitter Connect Plugin - ' . + "Connected Twitter user $this->fbuid to local user $user->id"); + + // Return to Twitter connection settings tab + common_redirect(common_local_url('twittersettings'), 303); + } + + function tryLogin() + { + common_debug('Twitter Connect Plugin - ' . + "Trying login for Twitter user $this->fbuid."); + + $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); + + if (!empty($flink)) { + $user = $flink->getUser(); + + if (!empty($user)) { + + common_debug('Twitter Connect Plugin - ' . + "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); + + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); + } + + } else { + + common_debug('Twitter Connect Plugin - ' . + "No flink found for twuid: $this->twuid - new user"); + + $this->showForm(null, $this->bestNewNickname()); + } + } + + function goHome($nickname) + { + $url = common_get_returnto(); + if ($url) { + // We don't have to return to it again + common_set_returnto(null); + } else { + $url = common_local_url('all', + array('nickname' => + $nickname)); + } + + common_redirect($url, 303); + } + + function flinkUser($user_id, $twuid) + { + $flink = new Foreign_link(); + + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; + $flink->service = TWITTER_SERVICE; + + $creds = TwitterOAuthClient::packToken($this->access_token); + + $flink->credentials = $creds; + $flink->created = common_sql_now(); + + // Defaults: noticesync on, everything else off + + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + if (empty($flink_id)) { + common_log_db_error($flink, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + save_twitter_user($twuid, $this->tw_fields['name']); + + return $flink_id; + } + + + function bestNewNickname() + { + if (!empty($this->tw_fields['name'])) { + $nickname = $this->nicknamize($this->tw_fields['name']); + if ($this->isNewNickname($nickname)) { + return $nickname; + } + } + + return null; + } + + // Given a string, try to make it work as a nickname + + function nicknamize($str) + { + $str = preg_replace('/\W/', '', $str); + $str = str_replace(array('-', '_'), '', $str); + return strtolower($str); + } + + function isNewNickname($str) + { + if (!Validate::string($str, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + return false; + } + if (!User::allowed_nickname($str)) { + return false; + } + if (User::staticGet('nickname', $str)) { + return false; + } + return true; + } + } diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php new file mode 100644 index 0000000000..ae468ea15c --- /dev/null +++ b/plugins/TwitterBridge/twitterlogin.php @@ -0,0 +1,95 @@ +. + * + * @category Settings + * @package StatusNet + * @author Evan Prodromou + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; + +/** + * Settings for Twitter integration + * + * @category Settings + * @package StatusNet + * @author Evan Prodromou + * @author Julien Chaumond + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see SettingsAction + */ + + +class TwitterloginAction extends Action +{ + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + $this->clientError(_('Already logged in.')); + } + + $this->showPage(); + } + + function title() + { + return _('Twitter Login'); + } + + function getInstructions() + { + return _('Login with your Twitter Account'); + } + + function showPageNotice() + { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); + } + + function showContent() + { + $this->elementStart('a', array('href' => common_local_url('twitterauthorization'))); + $this->element('img', array('src' => common_path('plugins/TwitterBridge/twitter_connect.gif'), + 'alt' => 'Connect my Twitter account')); + $this->elementEnd('a'); + } + + function showLocalNav() + { + $nav = new LoginGroupNav($this); + $nav->show(); + } +} From 2d97e15cd6b5fbda82218b97ce287a56e2665fb9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 01:25:33 +0000 Subject: [PATCH 093/130] - Twitter username wasn't getting stored in Foreign_user when linking Twitter account (fixed) - Updates to comments --- .../TwitterBridge/twitterauthorization.php | 109 ++++++++++-------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 8ae725374f..3f7316b7a4 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -53,7 +53,7 @@ class TwitterauthorizationAction extends Action var $twuid = null; var $tw_fields = null; var $access_token = null; - + /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -80,31 +80,37 @@ class TwitterauthorizationAction extends Action function handle($args) { parent::handle($args); - + if (common_logged_in()) { $user = common_current_user(); $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - + // If there's already a foreign link record, it means we already // have an access token, and this is unecessary. So go back. - + if (isset($flink)) { common_redirect(common_local_url('twittersettings')); } } - - + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // User was not logged in to StatusNet before + $this->twuid = $this->trimmed('twuid'); - $this->tw_fields = array("name" => $this->trimmed('name'), "fullname" => $this->trimmed('fullname')); + + $this->tw_fields = array('name' => $this->trimmed('tw_fields_name'), + 'fullname' => $this->trimmed('tw_fields_fullname')); + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { $this->showForm(_('There was a problem with your session token. Try again, please.')); return; } + if ($this->arg('create')) { if (!$this->boolean('license')) { $this->showForm(_('You can\'t register if you don\'t agree to the license.'), @@ -124,7 +130,7 @@ class TwitterauthorizationAction extends Action // $this->oauth_token is only populated once Twitter authorizes our // request token. If it's empty we're at the beginning of the auth // process - + if (empty($this->oauth_token)) { $this->authorizeRequestToken(); } else { @@ -181,6 +187,8 @@ class TwitterauthorizationAction extends Action $this->serverError(_m('Couldn\'t link your Twitter account.')); } + $twitter_user = null; + try { $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], @@ -196,16 +204,19 @@ class TwitterauthorizationAction extends Action $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s', + $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s', $e->getCode(), $e->getMessage()); $this->serverError(_m('Couldn\'t link your Twitter account.')); } if (common_logged_in()) { + // Save the access token and Twitter user info + $this->saveForeignLink($atok, $twitter_user); - } - else{ + + } else { + $this->twuid = $twitter_user->id; $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); $this->access_token = $atok; @@ -216,7 +227,7 @@ class TwitterauthorizationAction extends Action unset($_SESSION['twitter_request_token']); unset($_SESSION['twitter_request_token_secret']); - + if (common_logged_in()) { common_redirect(common_local_url('twittersettings')); } @@ -260,8 +271,34 @@ class TwitterauthorizationAction extends Action save_twitter_user($twitter_user->id, $twitter_user->screen_name); } + function flinkUser($user_id, $twuid) + { + $flink = new Foreign_link(); + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; + $flink->service = TWITTER_SERVICE; + $creds = TwitterOAuthClient::packToken($this->access_token); + + $flink->credentials = $creds; + $flink->created = common_sql_now(); + + // Defaults: noticesync on, everything else off + + $flink->set_flags(true, false, false, false); + + $flink_id = $flink->insert(); + + if (empty($flink_id)) { + common_log_db_error($flink, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + save_twitter_user($twuid, $this->tw_fields['name']); + + return $flink_id; + } function showPageNotice() { @@ -430,7 +467,7 @@ class TwitterauthorizationAction extends Action common_set_user($user); common_real_login(true); - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Registered new user $user->id from Twitter user $this->fbuid"); common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), @@ -450,7 +487,7 @@ class TwitterauthorizationAction extends Action $user = User::staticGet('nickname', $nickname); if (!empty($user)) { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Legit user to connect to Twitter: $nickname"); } @@ -461,7 +498,7 @@ class TwitterauthorizationAction extends Action return; } - common_debug('Twitter Connnect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Connected Twitter user $this->fbuid to local user $user->id"); common_set_user($user); @@ -481,17 +518,17 @@ class TwitterauthorizationAction extends Action return; } - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Connected Twitter user $this->fbuid to local user $user->id"); // Return to Twitter connection settings tab common_redirect(common_local_url('twittersettings'), 303); } - + function tryLogin() { - common_debug('Twitter Connect Plugin - ' . - "Trying login for Twitter user $this->fbuid."); + common_debug('TwitterBridge Plugin - ' . + "Trying login for Twitter user $this->twuid."); $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); @@ -500,7 +537,7 @@ class TwitterauthorizationAction extends Action if (!empty($user)) { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); common_set_user($user); @@ -510,7 +547,7 @@ class TwitterauthorizationAction extends Action } else { - common_debug('Twitter Connect Plugin - ' . + common_debug('TwitterBridge Plugin - ' . "No flink found for twuid: $this->twuid - new user"); $this->showForm(null, $this->bestNewNickname()); @@ -531,37 +568,7 @@ class TwitterauthorizationAction extends Action common_redirect($url, 303); } - - function flinkUser($user_id, $twuid) - { - $flink = new Foreign_link(); - $flink->user_id = $user_id; - $flink->foreign_id = $twuid; - $flink->service = TWITTER_SERVICE; - - $creds = TwitterOAuthClient::packToken($this->access_token); - - $flink->credentials = $creds; - $flink->created = common_sql_now(); - - // Defaults: noticesync on, everything else off - - $flink->set_flags(true, false, false, false); - - $flink_id = $flink->insert(); - - if (empty($flink_id)) { - common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_('Couldn\'t link your Twitter account.')); - } - - save_twitter_user($twuid, $this->tw_fields['name']); - - return $flink_id; - } - - function bestNewNickname() { if (!empty($this->tw_fields['name'])) { From 1c1abfc284c3b81cfcd4cd95b80a0e5c120bd962 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 01:51:40 +0000 Subject: [PATCH 094/130] Ask the user to set a password before disconnecting from Twitter --- plugins/TwitterBridge/twittersettings.php | 33 ++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php index bc9a636a15..0137060e9c 100644 --- a/plugins/TwitterBridge/twittersettings.php +++ b/plugins/TwitterBridge/twittersettings.php @@ -121,8 +121,35 @@ class TwittersettingsAction extends ConnectSettingsAction $this->elementEnd('p'); $this->element('p', 'form_note', _m('Connected Twitter account')); + $this->elementEnd('fieldset'); - $this->submit('remove', _m('Remove')); + $this->elementStart('fieldset'); + + $this->element('legend', null, _m('Disconnect my account from Twitter')); + + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_m('Disconnecting your Twitter ' . + 'could make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _m('set a password')); + + $this->text(_m(' first.')); + $this->elementEnd('p'); + } else { + + $note = _m('Keep your %1$s account but disconnect from Twitter. ' . + 'You can use your %1$s password to log in.'); + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site)); + + $this->submit('disconnect', _m('Disconnect')); + } $this->elementEnd('fieldset'); @@ -205,7 +232,7 @@ class TwittersettingsAction extends ConnectSettingsAction if ($this->arg('save')) { $this->savePreferences(); - } else if ($this->arg('remove')) { + } else if ($this->arg('disconnect')) { $this->removeTwitterAccount(); } else { $this->showForm(_m('Unexpected form submission.')); @@ -231,7 +258,7 @@ class TwittersettingsAction extends ConnectSettingsAction return; } - $this->showForm(_m('Twitter account removed.'), true); + $this->showForm(_m('Twitter account disconnected.'), true); } /** From ce44008d13e39d3e3a95af7d422a82f5ecce61e0 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 02:40:44 +0000 Subject: [PATCH 095/130] Use "Sign in with Twitter" auth pattern and official Twitter button for Twitter-based login. See: http://apiwiki.twitter.com/Sign-in-with-Twitter --- plugins/TwitterBridge/twitter_connect.gif | Bin 2205 -> 0 bytes plugins/TwitterBridge/twitterauthorization.php | 8 +++++--- plugins/TwitterBridge/twitterlogin.php | 11 ++++++----- plugins/TwitterBridge/twitteroauthclient.php | 7 +++++-- 4 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 plugins/TwitterBridge/twitter_connect.gif diff --git a/plugins/TwitterBridge/twitter_connect.gif b/plugins/TwitterBridge/twitter_connect.gif deleted file mode 100644 index e3d8f3ed7b88b41d67b37f8be92f7af532f25816..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2205 zcmV;O2x9j~Nk%w1VZ#6!0OkMyGOg67-0df;(tOI~iqPgGrqG4s`T%UCxby!1|NoiR z>U7=s1AMdT`u<+N-BG#QRK4EB^Zq5M(rv}y7M#u^rO(Ca^Et8E-TMFJ`~FDL^d_m& z)9v;kq|V9r{#DfX;`8}Eve_!A)uZeE@&5l)!QoB5-kj|Hw&L)u@BPyF{urLnAE(ej zver1x^fa&6@cH}_k-@g%?_j{*LbTXLwcN7q{)x}#PPo~V>HH9lzii$4dEogls@Ef@ z(E0rS-uM5??Dvb&=MRp+5RbqptJ5f}(jTPHD6G>UrqCXx&m^ePDXh~Wr_djz&`rMG z1ADR_rq3s=(oVnL0BfTlrO+Rx&mN@CO1Rt;na)PI-NfheLbBN*rqCj!(?ho0A)wG- zz~3pS)Fi0Y8lTW0q|+p;(Lb@)GqKb?wcH||$@%#F&h7Z^_W4e?*)_7;!|(k;&-9nt z@g}IzF09r>x7hLd{3WB(_W1j|?EUrq{-*H$)babK<@2rL@&Et+A^8LV00000EC2ui z0K)(o000O7fKX5jgM|!-g@cESjEIDaj*Ehmh>(_+jgx|ejhv5>mywd2pP!1Dq@s+Q zn4XZ5kEEKZu&<7Zhf`BeySu!;Pr<)WCcbLA$h^P0!ph9Ty~xSW!N9}P)X&k?%e~#e zy(ZVg&d}b?;>^0m=DySC&C$ityX@B6hekz6MNCHgNB{;D$b<>N7)A;zT*$DY!-o(f zN}SlRh{cN-Giuz(F{6=>AVZ2ANwVa~B1QyZS;?}c%a(bL!m5v!~CW zKzZ66N)+V2Lt2v7NXoRS)220Ibeu}Hs@1DiDQexy5UW>?2?x~}OIFU0KWre7UCTCx z!Ju&e;~sqrU)3KbX(}ykl+JX3MK>yFa*#gfF+{vfEi|>fkFZ> zC~)2cbHXsddI?x?o|b-2kU#(%AV{cSVesjvaVQvP!2=RRDnW$z zDSJXF8mR(eqVPhY$64TKiw{b0NC+>)`ods)Xo$eAx1Nx}8r8`f!xHZWkbs@;m1qH` zFsvvT2ttS%pmBA!X&wi4&KaPfg9;IbmV*v(-V3E|fGG$Y2s^M*}&ekirQRQ)~eL26!~g zFcCg@c_n}z3kt%O!UnK}U=u7c@Dj!;umrX*C+L94K9op+4>40Eg(g}cxAkfg~5}hvbbkk)by~NZYIN$)jG!#sx$7rXmwwE0r zqA?u_WRSxMBiLYr#3LXuC>#0v%LmFC;$M&B!e3SU;-7m!M|<*Lws#v z02Rn#4q*TT5LAGE2{=OzcUA!#;t+i|sKC~ASb;PA%K;T|1Dyg;0WtY7h6&h06Q5Yb zEDoRw=;Oc*6p%z2YLSU(Orsjts6#?{;f-GCK^s)Cg9qy22X-Jq0|Zk7IKEK_1?Ykx z2ib)r3;=yw8bA`j#6lbha%WwDUl&Z6gslk@2Qm;72Vhu*0hCDpPZgMez0g=PEOJ1H zL=@$q&X+#-v5x@w8i4s?SpfPe;C)$qA1zfG02KxRUmz%B7THM5V(PF9gBSxG0bmCV zeBgl#;K2eGumc_lW(>-FBN39&gl>BCo8SzmILArOa+>p;-AsZy*U8Ryy7QgzjHf!$ zNzZetlMrG6gFf#Ngamxx1Ja!4G+i))1c)J@`Wylaww79YPYEK+~Gq^rkq?sZMEHg`WEK zr$7y=P=`uXpYHUiNKL9zmx|OX9N`cmT|r30Aeaci!KxAZ$ODx6AW|}{z=B5zt60ZM z*0P%QtY}TETGz_fwz_q!#*C|6b!deS41xl9%_{}ozz4^bMh;%p>kumN*2123u82*n zViRlF#*(!HRyc%SCrjDNQeX&5$gE~J%h}F)_OqH5s})8|+R~c#w5Uz3YNNo~*1Gn! zu#K&3S$l=r+V-}%&8=>Cn_DGB;SgB}u5gD-+~Njz2v-2Da+k~8<~sMe(2cHVXG`7c zS~m(xIK%signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); return true; @@ -160,7 +162,7 @@ class TwitterauthorizationAction extends Action $_SESSION['twitter_request_token'] = $req_tok->key; $_SESSION['twitter_request_token_secret'] = $req_tok->secret; - $auth_link = $client->getAuthorizeLink($req_tok); + $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php index ae468ea15c..ae67b4c154 100644 --- a/plugins/TwitterBridge/twitterlogin.php +++ b/plugins/TwitterBridge/twitterlogin.php @@ -46,7 +46,6 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * @see SettingsAction */ - class TwitterloginAction extends Action { function handle($args) @@ -67,7 +66,7 @@ class TwitterloginAction extends Action function getInstructions() { - return _('Login with your Twitter Account'); + return _('Login with your Twitter account'); } function showPageNotice() @@ -81,9 +80,11 @@ class TwitterloginAction extends Action function showContent() { - $this->elementStart('a', array('href' => common_local_url('twitterauthorization'))); - $this->element('img', array('src' => common_path('plugins/TwitterBridge/twitter_connect.gif'), - 'alt' => 'Connect my Twitter account')); + $this->elementStart('a', array('href' => common_local_url('twitterauthorization', + null, + array('signin' => true)))); + $this->element('img', array('src' => common_path('plugins/TwitterBridge/Sign-in-with-Twitter-lighter.png'), + 'alt' => 'Sign in with Twitter')); $this->elementEnd('a'); } diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index bad2b74ca3..277e7ab409 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -45,6 +45,7 @@ class TwitterOAuthClient extends OAuthClient { public static $requestTokenURL = 'https://twitter.com/oauth/request_token'; public static $authorizeURL = 'https://twitter.com/oauth/authorize'; + public static $signinUrl = 'https://twitter.com/oauth/authenticate'; public static $accessTokenURL = 'https://twitter.com/oauth/access_token'; /** @@ -97,9 +98,11 @@ class TwitterOAuthClient extends OAuthClient * * @return the link */ - function getAuthorizeLink($request_token) + function getAuthorizeLink($request_token, $signin = false) { - return parent::getAuthorizeLink(self::$authorizeURL, + $url = ($signin) ? self::$signinUrl : self::$authorizeURL; + + return parent::getAuthorizeLink($url, $request_token, common_local_url('twitterauthorization')); } From f7450d2ca8b420260caf69bf1cded687d8eaf7b5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 07:29:40 +0000 Subject: [PATCH 096/130] - Remove redundant function - clean up log msgs --- .../TwitterBridge/twitterauthorization.php | 76 +++++++------------ 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 15408668fc..a95cdebb97 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -101,7 +101,7 @@ class TwitterauthorizationAction extends Action $this->twuid = $this->trimmed('twuid'); - $this->tw_fields = array('name' => $this->trimmed('tw_fields_name'), + $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), 'fullname' => $this->trimmed('tw_fields_fullname')); $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); @@ -215,12 +215,15 @@ class TwitterauthorizationAction extends Action // Save the access token and Twitter user info - $this->saveForeignLink($atok, $twitter_user); + $user = common_current_user(); + $this->saveForeignLink($user->id, $twitter_user->id, $atok); + save_twitter_user($twitter_user->id, $twitter_user->name); } else { $this->twuid = $twitter_user->id; - $this->tw_fields = array("name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); + $this->tw_fields = array("screen_name" => $twitter_user->screen_name, + "name" => $twitter_user->name); $this->access_token = $atok; $this->tryLogin(); } @@ -239,19 +242,18 @@ class TwitterauthorizationAction extends Action * Saves a Foreign_link between Twitter user and local user, * which includes the access token and secret. * - * @param OAuthToken $access_token the access token to save - * @param mixed $twitter_user twitter API user object + * @param int $user_id StatusNet user ID + * @param int $twuid Twitter user ID + * @param OAuthToken $token the access token to save * * @return nothing */ - function saveForeignLink($access_token, $twitter_user) + function saveForeignLink($user_id, $twuid, $access_token) { - $user = common_current_user(); - $flink = new Foreign_link(); - $flink->user_id = $user->id; - $flink->foreign_id = $twitter_user->id; + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; $flink->service = TWITTER_SERVICE; $creds = TwitterOAuthClient::packToken($access_token); @@ -265,40 +267,11 @@ class TwitterauthorizationAction extends Action $flink_id = $flink->insert(); - if (empty($flink_id)) { - common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_m('Couldn\'t link your Twitter account.')); - } - - save_twitter_user($twitter_user->id, $twitter_user->screen_name); - } - - function flinkUser($user_id, $twuid) - { - $flink = new Foreign_link(); - - $flink->user_id = $user_id; - $flink->foreign_id = $twuid; - $flink->service = TWITTER_SERVICE; - - $creds = TwitterOAuthClient::packToken($this->access_token); - - $flink->credentials = $creds; - $flink->created = common_sql_now(); - - // Defaults: noticesync on, everything else off - - $flink->set_flags(true, false, false, false); - - $flink_id = $flink->insert(); - if (empty($flink_id)) { common_log_db_error($flink, 'INSERT', __FILE__); $this->serverError(_('Couldn\'t link your Twitter account.')); } - save_twitter_user($twuid, $this->tw_fields['name']); - return $flink_id; } @@ -361,8 +334,8 @@ class TwitterauthorizationAction extends Action $this->hidden('access_token_key', $this->access_token->key); $this->hidden('access_token_secret', $this->access_token->secret); $this->hidden('twuid', $this->twuid); + $this->hidden('tw_fields_screen_name', $this->tw_fields['screen_name']); $this->hidden('tw_fields_name', $this->tw_fields['name']); - $this->hidden('tw_fields_fullname', $this->tw_fields['fullname']); $this->elementStart('fieldset'); $this->hidden('token', common_session_token()); @@ -449,7 +422,7 @@ class TwitterauthorizationAction extends Action return; } - $fullname = trim($this->tw_fields['fullname']); + $fullname = trim($this->tw_fields['name']); $args = array('nickname' => $nickname, 'fullname' => $fullname); @@ -459,7 +432,11 @@ class TwitterauthorizationAction extends Action $user = User::register($args); - $result = $this->flinkUser($user->id, $this->twuid); + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); if (!$result) { $this->serverError(_('Error connecting user to Twitter.')); @@ -470,7 +447,7 @@ class TwitterauthorizationAction extends Action common_real_login(true); common_debug('TwitterBridge Plugin - ' . - "Registered new user $user->id from Twitter user $this->fbuid"); + "Registered new user $user->id from Twitter user $this->twuid"); common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), 303); @@ -493,7 +470,11 @@ class TwitterauthorizationAction extends Action "Legit user to connect to Twitter: $nickname"); } - $result = $this->flinkUser($user->id, $this->twuid); + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); if (!$result) { $this->serverError(_('Error connecting user to Twitter.')); @@ -501,7 +482,7 @@ class TwitterauthorizationAction extends Action } common_debug('TwitterBridge Plugin - ' . - "Connected Twitter user $this->fbuid to local user $user->id"); + "Connected Twitter user $this->twuid to local user $user->id"); common_set_user($user); common_real_login(true); @@ -521,7 +502,7 @@ class TwitterauthorizationAction extends Action } common_debug('TwitterBridge Plugin - ' . - "Connected Twitter user $this->fbuid to local user $user->id"); + "Connected Twitter user $this->twuid to local user $user->id"); // Return to Twitter connection settings tab common_redirect(common_local_url('twittersettings'), 303); @@ -532,7 +513,8 @@ class TwitterauthorizationAction extends Action common_debug('TwitterBridge Plugin - ' . "Trying login for Twitter user $this->twuid."); - $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); + $flink = Foreign_link::getByForeignID($this->twuid, + TWITTER_SERVICE); if (!empty($flink)) { $user = $flink->getUser(); From 02957d28541b3164df117d54788d1152c85ff00b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 07:50:01 +0000 Subject: [PATCH 097/130] Add Julien C to author comments --- plugins/TwitterBridge/TwitterBridgePlugin.php | 12 +++++------- plugins/TwitterBridge/twitterauthorization.php | 8 +++++--- plugins/TwitterBridge/twitterlogin.php | 15 ++++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index e39ec7be03..c7f57ffc77 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -20,7 +20,8 @@ * @category Plugin * @package StatusNet * @author Zach Copley - * @copyright 2009 Control Yourself, Inc. + * @author Julien C + * @copyright 2009-2010 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ */ @@ -41,6 +42,7 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9'); * @category Plugin * @package StatusNet * @author Zach Copley + * @author Julien C * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * @link http://twitter.com/ @@ -72,16 +74,13 @@ class TwitterBridgePlugin extends Plugin $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); - $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); return true; } - - - + /* - * Add a login tab for Twitter Connect + * Add a login tab for 'Sign in with Twitter' * * @param Action &action the current action * @@ -99,7 +98,6 @@ class TwitterBridgePlugin extends Plugin return true; } - /** * Add the Twitter Settings page to the Connect Settings menu diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index a95cdebb97..b2657ff61f 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -19,10 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category TwitterauthorizationAction + * @category Plugin * @package StatusNet * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @author Julien C + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -41,9 +42,10 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * (Foreign_link) between the StatusNet user and Twitter user and stores the * access token and secret in the link. * - * @category Twitter + * @category Plugin * @package StatusNet * @author Zach Copley + * @author Julien C * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ * diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php index ae67b4c154..79421fb27d 100644 --- a/plugins/TwitterBridge/twitterlogin.php +++ b/plugins/TwitterBridge/twitterlogin.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Settings for Twitter integration + * 'Sign in with Twitter' login page * * PHP version 5 * @@ -19,10 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Settings + * @category Login * @package StatusNet - * @author Evan Prodromou - * @copyright 2008-2009 StatusNet, Inc. + * @author Julien Chaumond + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -34,12 +35,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; /** - * Settings for Twitter integration + * Page for logging in with Twitter * - * @category Settings + * @category Login * @package StatusNet - * @author Evan Prodromou * @author Julien Chaumond + * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * From e05c32572234dc0b4adb19be7bbe9879f49ec7ee Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 26 Jan 2010 19:13:05 +0100 Subject: [PATCH 098/130] Updated geolocation sharing in notice form for Realtime pop --- plugins/Realtime/realtimeupdate.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index 56f869354d..31e7c2ae66 100644 --- a/plugins/Realtime/realtimeupdate.css +++ b/plugins/Realtime/realtimeupdate.css @@ -18,7 +18,8 @@ display:none; } .realtime-popup #form_notice label[for=notice_data-attach], -.realtime-popup #form_notice #notice_data-attach { +.realtime-popup #form_notice #notice_data-attach, +.realtime-popup #form_notice label[for=notice_data-geo] { top:0; } From ad6f0501ff24cb287dedd21271d58c91e64b6b43 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 26 Jan 2010 09:25:39 -0800 Subject: [PATCH 099/130] Site metadata tags in status_network: single 'tags' field, pipe-separated. $sn->tags() returns tag list as array; $sn->hasTag('blah') to check for a particular tag only Could be used to control things in config file: $sn = Status_network::setupSite($_server, $_path, $_wildcard); if (!$sn) { die("No such site"); } if ($sn->hasTag('individual')) { /* blah */ } Note memcached keys are unchanged; if tags are changed from an external tool clear: statusnet::status_network:: for s 'nickname', 'hostname', and 'pathname' --- classes/Status_network.php | 20 ++++++++++++++++++++ db/site.sql | 2 ++ 2 files changed, 22 insertions(+) diff --git a/classes/Status_network.php b/classes/Status_network.php index 445f8a5a3c..f1314d6151 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -39,6 +39,7 @@ class Status_network extends DB_DataObject public $logo; // varchar(255) public $created; // datetime() not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + public $tags; // text /* Static get */ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); } @@ -245,4 +246,23 @@ class Status_network extends DB_DataObject return $this->nickname . '.' . self::$wildcard; } } + + /** + * Return site meta-info tags as an array + * @return array of strings + */ + function getTags() + { + return array_filter(explode("|", strval($this->tags))); + } + + /** + * Check if this site record has a particular meta-info tag attached. + * @param string $tag + * @return bool + */ + function hasTag($tag) + { + return in_array($tag, $this->getTags()); + } } diff --git a/db/site.sql b/db/site.sql index a9f64e5a5d..791303bd54 100644 --- a/db/site.sql +++ b/db/site.sql @@ -14,6 +14,8 @@ create table status_network ( sitename varchar(255) comment 'display name', theme varchar(255) comment 'theme name', logo varchar(255) comment 'site logo', + + tags text comment 'site meta-info tags (pipe-separated)', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' From 58be61b6417119de1b03ef50e166369c4005e4d1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 26 Jan 2010 11:49:49 -0800 Subject: [PATCH 100/130] Control channel for queue daemons to request graceful shutdown, restart, or update to listen to a newly added or reconfigured site. queuectl.php --update -s queuectl.php --stop queuectl.php --restart Default control channel is /topic/statusnet-control. For external utilities to send a site update ping direct to the queue server, connect via Stomp and send a message formatted thus: update: (Nickname here, *not* server hostname! The rest of the queues will be updated to use nicknames later.) Note that all currently-connected queue daemons will get these notifications, including both queuedaemon.php and xmppdaemon.php. (XMPP will ignore site update requests for sites that it's not handling.) Limitations: * only implemented for stomp queue manager so far * --update may not yet handle a changed server name properly * --restart won't reload PHP code files that were already loaded at startup. Still need to stop and restart the daemons from 'outside' when updating code base. --- classes/Status_network.php | 11 ++- lib/default.php | 1 + lib/iomaster.php | 53 ++++++++++--- lib/queuemanager.php | 17 ++++ lib/spawningdaemon.php | 58 ++++++++++---- lib/stompqueuemanager.php | 159 ++++++++++++++++++++++++++++++++++--- scripts/queuectl.php | 85 ++++++++++++++++++++ scripts/queuedaemon.php | 2 +- scripts/xmppdaemon.php | 2 +- 9 files changed, 347 insertions(+), 41 deletions(-) create mode 100755 scripts/queuectl.php diff --git a/classes/Status_network.php b/classes/Status_network.php index f1314d6151..4bda24b6a0 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -42,7 +42,16 @@ class Status_network extends DB_DataObject public $tags; // text /* Static get */ - function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); } + function staticGet($k,$v=NULL) { + $i = DB_DataObject::staticGet('Status_network',$k,$v); + + // Don't use local process cache; if we're fetching multiple + // times it's because we're reloading it in a long-running + // process; we need a fresh copy! + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CACHE']['status_network']); + return $i; + } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE diff --git a/lib/default.php b/lib/default.php index 35115542f2..d2dd8ab33c 100644 --- a/lib/default.php +++ b/lib/default.php @@ -81,6 +81,7 @@ $default = 'subsystem' => 'db', # default to database, or 'stomp' 'stomp_server' => null, 'queue_basename' => '/queue/statusnet/', + 'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons 'stomp_username' => null, 'stomp_password' => null, 'monitor' => null, // URL to monitor ping endpoint (work in progress) diff --git a/lib/iomaster.php b/lib/iomaster.php index 3bf82bc6b4..bcab3542be 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -38,6 +38,9 @@ abstract class IoMaster protected $pollTimeouts = array(); protected $lastPoll = array(); + public $shutdown = false; // Did we do a graceful shutdown? + public $respawn = true; // Should we respawn after shutdown? + /** * @param string $id process ID to use in logging/monitoring */ @@ -144,7 +147,7 @@ abstract class IoMaster $this->logState('init'); $this->start(); - while (true) { + while (!$this->shutdown) { $timeouts = array_values($this->pollTimeouts); $timeouts[] = 60; // default max timeout @@ -196,22 +199,31 @@ abstract class IoMaster $this->logState('idle'); $this->idle(); - $memoryLimit = $this->softMemoryLimit(); - if ($memoryLimit > 0) { - $usage = memory_get_usage(); - if ($usage > $memoryLimit) { - common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); - break; - } else if (common_config('queue', 'debug_memory')) { - common_log(LOG_DEBUG, "Memory usage $usage"); - } - } + $this->checkMemory(); } $this->logState('shutdown'); $this->finish(); } + /** + * Check runtime memory usage, possibly triggering a graceful shutdown + * and thread respawn if we've crossed the soft limit. + */ + protected function checkMemory() + { + $memoryLimit = $this->softMemoryLimit(); + if ($memoryLimit > 0) { + $usage = memory_get_usage(); + if ($usage > $memoryLimit) { + common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); + $this->requestRestart(); + } else if (common_config('queue', 'debug_memory')) { + common_log(LOG_DEBUG, "Memory usage $usage"); + } + } + } + /** * Return fully-parsed soft memory limit in bytes. * @return intval 0 or -1 if not set @@ -354,5 +366,24 @@ abstract class IoMaster $owners[] = "thread:" . $this->id; $this->monitor->stats($key, $owners); } + + /** + * For IoManagers to request a graceful shutdown at end of event loop. + */ + public function requestShutdown() + { + $this->shutdown = true; + $this->respawn = false; + } + + /** + * For IoManagers to request a graceful restart at end of event loop. + */ + public function requestRestart() + { + $this->shutdown = true; + $this->respawn = true; + } + } diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 1bc8de1f78..afe710e884 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -100,6 +100,23 @@ abstract class QueueManager extends IoManager $this->initialize(); } + /** + * Optional; ping any running queue handler daemons with a notification + * such as announcing a new site to handle or requesting clean shutdown. + * This avoids having to restart all the daemons manually to update configs + * and such. + * + * Called from scripts/queuectl.php controller utility. + * + * @param string $event event key + * @param string $param optional parameter to append to key + * @return boolean success + */ + public function sendControlSignal($event, $param='') + { + throw new Exception(get_class($this) . " does not support control signals."); + } + /** * Store an object (usually/always a Notice) into the given queue * for later processing. No guarantee is made on when it will be diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php index 8baefe88e8..b1961d6880 100644 --- a/lib/spawningdaemon.php +++ b/lib/spawningdaemon.php @@ -36,6 +36,11 @@ abstract class SpawningDaemon extends Daemon { protected $threads=1; + const EXIT_OK = 0; + const EXIT_ERR = 1; + const EXIT_SHUTDOWN = 100; + const EXIT_RESTART = 101; + function __construct($id=null, $daemonize=true, $threads=1) { parent::__construct($daemonize); @@ -49,7 +54,7 @@ abstract class SpawningDaemon extends Daemon /** * Perform some actual work! * - * @return boolean true on success, false on failure + * @return int exit code; use self::EXIT_SHUTDOWN to request not to respawn. */ public abstract function runThread(); @@ -84,23 +89,30 @@ abstract class SpawningDaemon extends Daemon while (count($children) > 0) { $status = null; $pid = pcntl_wait($status); - if ($pid > 0) { + if ($pid > 0 && pcntl_wifexited($status)) { + $exitCode = pcntl_wexitstatus($status); + $i = array_search($pid, $children); if ($i === false) { - $this->log(LOG_ERR, "Unrecognized child pid $pid exited!"); + $this->log(LOG_ERR, "Unrecognized child pid $pid exited with status $exitCode"); continue; } unset($children[$i]); - $this->log(LOG_INFO, "Thread $i pid $pid exited."); - - $pid = pcntl_fork(); - if ($pid < 0) { - $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n"); - } else if ($pid == 0) { - $this->initAndRunChild($i); + + if ($this->shouldRespawn($exitCode)) { + $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; respawing."); + + $pid = pcntl_fork(); + if ($pid < 0) { + $this->log(LOG_ERROR, "Couldn't fork to respawn thread $i; aborting thread.\n"); + } else if ($pid == 0) { + $this->initAndRunChild($i); + } else { + $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); + $children[$i] = $pid; + } } else { - $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); - $children[$i] = $pid; + $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread."); } } } @@ -108,6 +120,24 @@ abstract class SpawningDaemon extends Daemon return true; } + /** + * Determine whether to respawn an exited subprocess based on its exit code. + * Otherwise we'll respawn all exits by default. + * + * @param int $exitCode + * @return boolean true to respawn + */ + protected function shouldRespawn($exitCode) + { + if ($exitCode == self::EXIT_SHUTDOWN) { + // Thread requested a clean shutdown. + return false; + } else { + // Otherwise we should always respawn! + return true; + } + } + /** * Initialize things for a fresh thread, call runThread(), and * exit at completion with appropriate return value. @@ -116,8 +146,8 @@ abstract class SpawningDaemon extends Daemon { $this->set_id($this->get_id() . "." . $thread); $this->resetDb(); - $ok = $this->runThread(); - exit($ok ? 0 : 1); + $exitCode = $this->runThread(); + exit($exitCode); } /** diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 8f0091a138..89f3d74cce 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -38,8 +38,10 @@ class StompQueueManager extends QueueManager var $password = null; var $base = null; var $con = null; + protected $control; protected $sites = array(); + protected $subscriptions = array(); protected $useTransactions = true; protected $transaction = null; @@ -52,6 +54,7 @@ class StompQueueManager extends QueueManager $this->username = common_config('queue', 'stomp_username'); $this->password = common_config('queue', 'stomp_password'); $this->base = common_config('queue', 'queue_basename'); + $this->control = common_config('queue', 'control_channel'); } /** @@ -77,6 +80,36 @@ class StompQueueManager extends QueueManager $this->initialize(); } + /** + * Optional; ping any running queue handler daemons with a notification + * such as announcing a new site to handle or requesting clean shutdown. + * This avoids having to restart all the daemons manually to update configs + * and such. + * + * Currently only relevant for multi-site queue managers such as Stomp. + * + * @param string $event event key + * @param string $param optional parameter to append to key + * @return boolean success + */ + public function sendControlSignal($event, $param='') + { + $message = $event; + if ($param != '') { + $message .= ':' . $param; + } + $this->_connect(); + $result = $this->con->send($this->control, + $message, + array ('created' => common_sql_now())); + if ($result) { + $this->_log(LOG_INFO, "Sent control ping to queue daemons: $message"); + return true; + } else { + $this->_log(LOG_ERR, "Failed sending control ping to queue daemons: $message"); + return false; + } + } /** * Instantiate the appropriate QueueHandler class for the given queue. @@ -86,7 +119,7 @@ class StompQueueManager extends QueueManager */ function getHandler($queue) { - $handlers = $this->handlers[common_config('site', 'server')]; + $handlers = $this->handlers[$this->currentSite()]; if (isset($handlers[$queue])) { $class = $handlers[$queue]; if (class_exists($class)) { @@ -108,7 +141,7 @@ class StompQueueManager extends QueueManager function getQueues() { $group = $this->activeGroup(); - $site = common_config('site', 'server'); + $site = $this->currentSite(); if (empty($this->groups[$site][$group])) { return array(); } else { @@ -126,8 +159,8 @@ class StompQueueManager extends QueueManager */ public function connect($transport, $class, $group='queuedaemon') { - $this->handlers[common_config('site', 'server')][$transport] = $class; - $this->groups[common_config('site', 'server')][$group][$transport] = $class; + $this->handlers[$this->currentSite()][$transport] = $class; + $this->groups[$this->currentSite()][$group][$transport] = $class; } /** @@ -180,7 +213,16 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleItem($frame); + $dest = $frame->headers['destination']; + if ($dest == $this->control) { + if (!$this->handleControlSignal($frame)) { + // We got a control event that requests a shutdown; + // close out and stop handling anything else! + break; + } + } else { + $ok = $ok && $this->handleItem($frame); + } } return $ok; } @@ -197,6 +239,9 @@ class StompQueueManager extends QueueManager public function start($master) { parent::start($master); + $this->_connect(); + + $this->con->subscribe($this->control); if ($this->sites) { foreach ($this->sites as $server) { StatusNet::init($server); @@ -221,6 +266,7 @@ class StompQueueManager extends QueueManager // If there are any outstanding delivered messages we haven't processed, // free them for another thread to take. $this->rollback(); + $this->con->unsubscribe($this->control); if ($this->sites) { foreach ($this->sites as $server) { StatusNet::init($server); @@ -231,7 +277,16 @@ class StompQueueManager extends QueueManager } return true; } - + + /** + * Get identifier of the currently active site configuration + * @return string + */ + protected function currentSite() + { + return common_config('site', 'server'); // @fixme switch to nickname + } + /** * Lazy open connection to Stomp queue server. */ @@ -255,22 +310,29 @@ class StompQueueManager extends QueueManager */ protected function doSubscribe() { + $site = $this->currentSite(); $this->_connect(); foreach ($this->getQueues() as $queue) { $rawqueue = $this->queueName($queue); + $this->subscriptions[$site][$queue] = $rawqueue; $this->_log(LOG_INFO, "Subscribing to $rawqueue"); $this->con->subscribe($rawqueue); } } - + /** * Subscribe from all enabled notice queues for the current site. */ protected function doUnsubscribe() { + $site = $this->currentSite(); $this->_connect(); - foreach ($this->getQueues() as $queue) { - $this->con->unsubscribe($this->queueName($queue)); + if (!empty($this->subscriptions[$site])) { + foreach ($this->subscriptions[$site] as $queue => $rawqueue) { + $this->_log(LOG_INFO, "Unsubscribing from $rawqueue"); + $this->con->unsubscribe($rawqueue); + unset($this->subscriptions[$site][$queue]); + } } } @@ -286,10 +348,10 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleItem($frame) + protected function handleItem($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); - if ($site != common_config('site', 'server')) { + if ($site != $this->currentSite()) { $this->stats('switch'); StatusNet::init($site); } @@ -317,7 +379,7 @@ class StompQueueManager extends QueueManager $handler = $this->getHandler($queue); if (!$handler) { - $this->_log(LOG_ERROR, "Missing handler class; skipping $info"); + $this->_log(LOG_ERR, "Missing handler class; skipping $info"); $this->ack($frame); $this->commit(); $this->begin(); @@ -348,6 +410,77 @@ class StompQueueManager extends QueueManager return true; } + /** + * Process a control signal broadcast. + * + * @param array $frame Stomp frame + * @return bool true to continue; false to stop further processing. + */ + protected function handleControlSignal($frame) + { + $message = trim($frame->body); + if (strpos($message, ':') !== false) { + list($event, $param) = explode(':', $message, 2); + } else { + $event = $message; + $param = ''; + } + + $shutdown = false; + + if ($event == 'shutdown') { + $this->master->requestShutdown(); + $shutdown = true; + } else if ($event == 'restart') { + $this->master->requestRestart(); + $shutdown = true; + } else if ($event == 'update') { + $this->updateSiteConfig($param); + } else { + $this->_log(LOG_ERR, "Ignoring unrecognized control message: $message"); + } + + $this->ack($frame); + $this->commit(); + $this->begin(); + return $shutdown; + } + + /** + * Set us up with queue subscriptions for a new site added at runtime, + * triggered by a broadcast to the 'statusnet-control' topic. + * + * @param array $frame Stomp frame + * @return bool true to continue; false to stop further processing. + */ + protected function updateSiteConfig($nickname) + { + if (empty($this->sites)) { + if ($nickname == common_config('site', 'nickname')) { + StatusNet::init(common_config('site', 'server')); + $this->doUnsubscribe(); + $this->doSubscribe(); + } else { + $this->_log(LOG_INFO, "Ignoring update ping for other site $nickname"); + } + } else { + $sn = Status_network::staticGet($nickname); + if ($sn) { + $server = $sn->getServerName(); // @fixme do config-by-nick + StatusNet::init($server); + if (empty($this->sites[$server])) { + $this->addSite($server); + } + $this->_log(LOG_INFO, "(Re)subscribing to queues for site $nickname / $server"); + $this->doUnsubscribe(); + $this->doSubscribe(); + $this->stats('siteupdate'); + } else { + $this->_log(LOG_ERR, "Ignoring ping for unrecognized new site $nickname"); + } + } + } + /** * Combines the queue_basename from configuration with the * site server name and queue name to give eg: @@ -360,7 +493,7 @@ class StompQueueManager extends QueueManager protected function queueName($queue) { return common_config('queue', 'queue_basename') . - common_config('site', 'server') . '/' . $queue; + $this->currentSite() . '/' . $queue; } /** diff --git a/scripts/queuectl.php b/scripts/queuectl.php new file mode 100755 index 0000000000..1c9ea33536 --- /dev/null +++ b/scripts/queuectl.php @@ -0,0 +1,85 @@ +#!/usr/bin/env php +. + */ + +/** + * Sends control signals to running queue daemons. + * + * @author Brion Vibber + * @package QueueHandler + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'ur'; +$longoptions = array('update', 'restart', 'stop'); + +$helptext = <<sendControlSignal($event, $param)) { + print " sent.\n"; + } else { + print " FAILED.\n"; + } +} + +$actions = 0; + +if (have_option('u') || have_option('--update')) { + $nickname = common_config('site', 'nickname'); + doSendControl("Sending site update signal to queue daemons for $nickname", + "update", $nickname); + $actions++; +} + +if (have_option('r') || have_option('--restart')) { + doSendControl("Sending graceful restart signal to queue daemons...", + "restart"); + $actions++; +} + +if (have_option('--stop')) { + doSendControl("Sending graceful shutdown signal to queue daemons...", + "shutdown"); + $actions++; +} + +if (!$actions) { + show_help(); +} + diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index bedd14b1a3..c2e2351c39 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -115,7 +115,7 @@ class QueueDaemon extends SpawningDaemon $this->log(LOG_INFO, 'terminating normally'); - return true; + return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN; } } diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php index fd7cf055b4..46dd9b90cc 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/xmppdaemon.php @@ -56,7 +56,7 @@ class XMPPDaemon extends SpawningDaemon common_log(LOG_INFO, 'terminating normally'); - return true; + return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN; } } From 03685bba1ea6c1b8f2c3db8d2f095a221b29a464 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 15:11:09 -0800 Subject: [PATCH 101/130] - Remove redudant/unused 'server' setting from site admin panel - Move 'fancy urls' checkbox from site admin panel to paths admin panel --- actions/pathsadminpanel.php | 33 ++++++++++++++++++++++++++++++--- actions/siteadminpanel.php | 21 +++------------------ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/actions/pathsadminpanel.php b/actions/pathsadminpanel.php index 3779fcfaaa..9155a7e428 100644 --- a/actions/pathsadminpanel.php +++ b/actions/pathsadminpanel.php @@ -24,7 +24,7 @@ * @author Evan Prodromou * @author Zach Copley * @author Sarven Capadisli - * @copyright 2008-2009 StatusNet, Inc. + * @copyright 2008-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -98,6 +98,11 @@ class PathsadminpanelAction extends AdminPanelAction 'background' => array('server', 'dir', 'path') ); + // XXX: If we're only going to have one boolean on thi page we + // can remove some of the boolean processing code --Z + + static $booleans = array('site' => array('fancy')); + $values = array(); foreach ($settings as $section => $parts) { @@ -106,6 +111,12 @@ class PathsadminpanelAction extends AdminPanelAction } } + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; + } + } + $this->validate($values); // assert(all values are valid); @@ -120,7 +131,13 @@ class PathsadminpanelAction extends AdminPanelAction } } - $config->query('COMMIT'); + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); return; } @@ -213,10 +230,14 @@ class PathsAdminPanelForm extends AdminForm function formData() { - $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale')); + $this->out->elementStart('fieldset', array('id' => 'settings_paths_locale')); $this->out->element('legend', null, _('Site'), 'site'); $this->out->elementStart('ul', 'form_data'); + $this->li(); + $this->input('server', _('Server'), _('Site\'s server hostname.')); + $this->unli(); + $this->li(); $this->input('path', _('Path'), _('Site path')); $this->unli(); @@ -225,6 +246,12 @@ class PathsAdminPanelForm extends AdminForm $this->input('locale_path', _('Path to locales'), _('Directory path to locales'), 'site'); $this->unli(); + $this->li(); + $this->out->checkbox('fancy', _('Fancy URLs'), + (bool) $this->value('fancy'), + _('Use fancy (more readable and memorable) URLs?')); + $this->unli(); + $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index dd388a18a2..1cb57ec091 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -24,7 +24,7 @@ * @author Evan Prodromou * @author Zach Copley * @author Sarven Capadisli - * @copyright 2008-2009 StatusNet, Inc. + * @copyright 2008-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -95,7 +95,7 @@ class SiteadminpanelAction extends AdminPanelAction 'site', 'textlimit', 'dupelimit'), 'snapshot' => array('run', 'reporturl', 'frequency')); - static $booleans = array('site' => array('private', 'inviteonly', 'closed', 'fancy')); + static $booleans = array('site' => array('private', 'inviteonly', 'closed')); $values = array(); @@ -299,22 +299,7 @@ class SiteAdminPanelForm extends AdminForm $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_urls')); - $this->out->element('legend', null, _('URLs')); - $this->out->elementStart('ul', 'form_data'); - $this->li(); - $this->input('server', _('Server'), _('Site\'s server hostname.')); - $this->unli(); - - $this->li(); - $this->out->checkbox('fancy', _('Fancy URLs'), - (bool) $this->value('fancy'), - _('Use fancy (more readable and memorable) URLs?')); - $this->unli(); - $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); - - $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); + $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); $this->out->element('legend', null, _('Access')); $this->out->elementStart('ul', 'form_data'); $this->li(); From aad42427ccd4d3824efcbce43d8e9dba6d5b475d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 26 Jan 2010 15:56:19 -0800 Subject: [PATCH 102/130] New access admin panel for site registration settings --- actions/accessadminpanel.php | 192 +++++++++++++++++++++++++++++++++++ actions/siteadminpanel.php | 37 ------- lib/adminpanelaction.php | 13 ++- lib/default.php | 2 +- lib/router.php | 1 + 5 files changed, 203 insertions(+), 42 deletions(-) create mode 100644 actions/accessadminpanel.php diff --git a/actions/accessadminpanel.php b/actions/accessadminpanel.php new file mode 100644 index 0000000000..4768e2faf9 --- /dev/null +++ b/actions/accessadminpanel.php @@ -0,0 +1,192 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Administer site access settings + * + * @category Admin + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class AccessadminpanelAction extends AdminPanelAction +{ + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + return _('Access'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + return _('Site access settings'); + } + + /** + * Show the site admin panel form + * + * @return void + */ + + function showForm() + { + $form = new AccessAdminPanelForm($this); + $form->show(); + return; + } + + /** + * Save settings from the form + * + * @return void + */ + + function saveSettings() + { + static $booleans = array('site' => array('private', 'inviteonly', 'closed')); + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; + } + } + + $config = new Config(); + + $config->query('BEGIN'); + + foreach ($booleans as $section => $parts) { + foreach ($parts as $setting) { + Config::save($section, $setting, $values[$section][$setting]); + } + } + + $config->query('COMMIT'); + + return; + } + +} + +class AccessAdminPanelForm extends AdminForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'form_site_admin_panel'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('accessadminpanel'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); + $this->out->element('legend', null, _('Registration')); + $this->out->elementStart('ul', 'form_data'); + $this->li(); + $this->out->checkbox('private', _('Private'), + (bool) $this->value('private'), + _('Prohibit anonymous users (not logged in) from viewing site?')); + $this->unli(); + + $this->li(); + $this->out->checkbox('inviteonly', _('Invite only'), + (bool) $this->value('inviteonly'), + _('Make registration invitation only.')); + $this->unli(); + + $this->li(); + $this->out->checkbox('closed', _('Closed'), + (bool) $this->value('closed'), + _('Disable new registrations.')); + $this->unli(); + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Save'), 'submit', null, _('Save access settings')); + } + +} diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index 1cb57ec091..8c8f8b3742 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -95,8 +95,6 @@ class SiteadminpanelAction extends AdminPanelAction 'site', 'textlimit', 'dupelimit'), 'snapshot' => array('run', 'reporturl', 'frequency')); - static $booleans = array('site' => array('private', 'inviteonly', 'closed')); - $values = array(); foreach ($settings as $section => $parts) { @@ -105,12 +103,6 @@ class SiteadminpanelAction extends AdminPanelAction } } - foreach ($booleans as $section => $parts) { - foreach ($parts as $setting) { - $values[$section][$setting] = ($this->boolean($setting)) ? 1 : 0; - } - } - // This throws an exception on validation errors $this->validate($values); @@ -127,12 +119,6 @@ class SiteadminpanelAction extends AdminPanelAction } } - foreach ($booleans as $section => $parts) { - foreach ($parts as $setting) { - Config::save($section, $setting, $values[$section][$setting]); - } - } - $config->query('COMMIT'); return; @@ -299,29 +285,6 @@ class SiteAdminPanelForm extends AdminForm $this->out->elementEnd('ul'); $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_access')); - $this->out->element('legend', null, _('Access')); - $this->out->elementStart('ul', 'form_data'); - $this->li(); - $this->out->checkbox('private', _('Private'), - (bool) $this->value('private'), - _('Prohibit anonymous users (not logged in) from viewing site?')); - $this->unli(); - - $this->li(); - $this->out->checkbox('inviteonly', _('Invite only'), - (bool) $this->value('inviteonly'), - _('Make registration invitation only.')); - $this->unli(); - - $this->li(); - $this->out->checkbox('closed', _('Closed'), - (bool) $this->value('closed'), - _('Disable new registrations.')); - $this->unli(); - $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); - $this->out->elementStart('fieldset', array('id' => 'settings_admin_snapshots')); $this->out->element('legend', null, _('Snapshots')); $this->out->elementStart('ul', 'form_data'); diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index a6981ac611..f62bfa458a 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -319,12 +319,17 @@ class AdminPanelNav extends Widget if ($this->canAdmin('user')) { $this->out->menuItem(common_local_url('useradminpanel'), _('User'), - _('Paths configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel'); + _('User configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel'); } - if ($this->canAdmin('paths')) { - $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'), - _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); + if ($this->canAdmin('access')) { + $this->out->menuItem(common_local_url('accessadminpanel'), _('Access'), + _('Access configuration'), $action_name == 'accessadminpanel', 'nav_design_admin_panel'); + } + + if ($this->canAdmin('paths')) { + $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'), + _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel'); } Event::handle('EndAdminPanelNav', array($this)); diff --git a/lib/default.php b/lib/default.php index d2dd8ab33c..37fb65b533 100644 --- a/lib/default.php +++ b/lib/default.php @@ -260,7 +260,7 @@ $default = 'OpenID' => null), ), 'admin' => - array('panels' => array('design', 'site', 'user', 'paths')), + array('panels' => array('design', 'site', 'user', 'paths', 'access')), 'singleuser' => array('enabled' => false, 'nickname' => null), diff --git a/lib/router.php b/lib/router.php index c0ddc8db38..03765b39dd 100644 --- a/lib/router.php +++ b/lib/router.php @@ -637,6 +637,7 @@ class Router $m->connect('admin/site', array('action' => 'siteadminpanel')); $m->connect('admin/design', array('action' => 'designadminpanel')); $m->connect('admin/user', array('action' => 'useradminpanel')); + $m->connect('admin/access', array('action' => 'accessadminpanel')); $m->connect('admin/paths', array('action' => 'pathsadminpanel')); $m->connect('getfile/:filename', From 492950b784ccf76ad96a24732c4329e3b1ca5ac8 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:36:13 +0100 Subject: [PATCH 103/130] Fix inconsistent title case in page title --- actions/oauthconnectionssettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index b17729b821..c2e8d441b0 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -68,7 +68,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction function title() { - return _('Connected Applications'); + return _('Connected applications'); } function isReadOnly($args) From 97e1acdc329c3f4dbabce37ff5985f655aa91541 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:37:06 +0100 Subject: [PATCH 104/130] Fix casing for HMAC-SHA1. --- actions/showapplication.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actions/showapplication.php b/actions/showapplication.php index 049206375d..a6ff425c7c 100644 --- a/actions/showapplication.php +++ b/actions/showapplication.php @@ -265,7 +265,7 @@ class ShowApplicationAction extends OwnerDesignAction $this->elementEnd('dl'); $this->element('p', 'note', - _('Note: We support hmac-sha1 signatures. We do not support the plaintext signature method.')); + _('Note: We support HMAC-SHA1 signatures. We do not support the plaintext signature method.')); $this->elementEnd('div'); $this->elementStart('p', array('id' => 'application_action')); @@ -325,4 +325,3 @@ class ShowApplicationAction extends OwnerDesignAction } } - From 4202ffff9184eaf0934184309ef8f3b9a90d60d9 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 14 Jan 2010 23:38:29 +0100 Subject: [PATCH 105/130] Make more complete sentence. --- lib/applicationeditform.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php index 040d3bf74b..6f03a9beda 100644 --- a/lib/applicationeditform.php +++ b/lib/applicationeditform.php @@ -203,7 +203,7 @@ class ApplicationEditForm extends Form $maxDesc = Oauth_application::maxDesc(); if ($maxDesc > 0) { - $descInstr = sprintf(_('Describe your application in %d chars'), + $descInstr = sprintf(_('Describe your application in %d characters'), $maxDesc); } else { $descInstr = _('Describe your application'); From 923b7de3c661098cfe3d5bdc0878d9329e1ee2bf Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 08:41:26 +0000 Subject: [PATCH 106/130] - Check for read-only vs. read-write access to protected API resources (OAuth) - Some cleanup --- actions/apistatusesupdate.php | 18 +++-- lib/apiauth.php | 126 ++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 69 deletions(-) diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 31c9b20ce2..bf367e1e18 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -28,7 +28,7 @@ * @author Mike Cochrane * @author Robin Millette * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -79,7 +79,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction { parent::prepare($args); - $this->user = $this->auth_user; $this->status = $this->trimmed('status'); $this->source = $this->trimmed('source'); $this->lat = $this->trimmed('lat'); @@ -145,7 +144,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction return; } - if (empty($this->user)) { + if (empty($this->auth_user)) { $this->clientError(_('No such user.'), 404, $this->format); return; } @@ -172,7 +171,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction // Check for commands $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($this->user, $status_shortened); + $cmd = $inter->handle_command($this->auth_user, $status_shortened); if ($cmd) { @@ -184,7 +183,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction // And, it returns your last status whether the cmd was successful // or not! - $this->notice = $this->user->getCurrentNotice(); + $this->notice = $this->auth_user->getCurrentNotice(); } else { @@ -211,7 +210,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction $upload = null; try { - $upload = MediaFile::fromUpload('media', $this->user); + $upload = MediaFile::fromUpload('media', $this->auth_user); } catch (ClientException $ce) { $this->clientError($ce->getMessage()); return; @@ -234,19 +233,19 @@ class ApiStatusesUpdateAction extends ApiAuthAction $options = array('reply_to' => $reply_to); - if ($this->user->shareLocation()) { + if ($this->auth_user->shareLocation()) { $locOptions = Notice::locationOptions($this->lat, $this->lon, null, null, - $this->user->getProfile()); + $this->auth_user->getProfile()); $options = array_merge($options, $locOptions); } $this->notice = - Notice::saveNew($this->user->id, + Notice::saveNew($this->auth_user->id, $content, $this->source, $options); @@ -255,7 +254,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction $upload->attachToNotice($this->notice); } - } $this->showNotice(); diff --git a/lib/apiauth.php b/lib/apiauth.php index 37070d212f..ad9651ff26 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -29,7 +29,7 @@ * @author mEDI * @author Sarven Capadisli * @author Zach Copley - * @copyright 2009 StatusNet, Inc. + * @copyright 2009-2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ @@ -53,9 +53,11 @@ require_once INSTALLDIR . '/lib/apioauth.php'; class ApiAuthAction extends ApiAction { - var $access_token; - var $oauth_access_type; - var $oauth_source; + var $auth_user_nickname = null; + var $auth_user_password = null; + var $access_token = null; + var $oauth_source = null; + var $auth_user = null; /** * Take arguments for running, and output basic auth header if needed @@ -70,18 +72,28 @@ class ApiAuthAction extends ApiAction { parent::prepare($args); + $this->consumer_key = $this->arg('oauth_consumer_key'); + $this->access_token = $this->arg('oauth_token'); + + // NOTE: $this->auth_user has to get set in prepare(), not handle(), + // because subclasses do stuff with it in their prepares. + if ($this->requiresAuth()) { - - $this->consumer_key = $this->arg('oauth_consumer_key'); - $this->access_token = $this->arg('oauth_token'); - if (!empty($this->access_token)) { $this->checkOAuthRequest(); } else { $this->checkBasicAuthUser(); - // By default, all basic auth users have read and write access + } - $this->access = self::READ_WRITE; + // Reject API calls with the wrong access level + + if ($this->isReadOnly($args) == false) { + if ($this->access != self::READ_WRITE) { + $msg = 'API resource requires read-write access, ' . + 'but you only have read access.'; + $this->clientError($msg, 401, $this->format); + exit(); + } } } @@ -95,8 +107,6 @@ class ApiAuthAction extends ApiAction function checkOAuthRequest() { - common_debug("We have an OAuth request."); - $datastore = new ApiStatusNetOAuthDataStore(); $server = new OAuthServer($datastore); $hmac_method = new OAuthSignatureMethod_HMAC_SHA1(); @@ -114,9 +124,10 @@ class ApiAuthAction extends ApiAction if (empty($app)) { - // this should really not happen - common_log(LOG_WARN, - "Couldn't find the OAuth app for consumer key: $this->consumer_key"); + // this should probably not happen + common_log(LOG_WARNING, + 'Couldn\'t find the OAuth app for consumer key: ' . + $this->consumer_key); throw new OAuthException('No application for that consumer key.'); } @@ -128,20 +139,18 @@ class ApiAuthAction extends ApiAction $appUser = Oauth_application_user::staticGet('token', $this->access_token); - // XXX: check that app->id and appUser->application_id and consumer all + // XXX: Check that app->id and appUser->application_id and consumer all // match? if (!empty($appUser)) { - // read or read-write - $this->oauth_access_type = $appUser->access_type; - // If access_type == 0 we have either a request token // or a bad / revoked access token - if ($this->oauth_access_type != 0) { + if ($appUser->access_type != 0) { + + // Set the access level for the api call - // Set the read or read-write access for the api call $this->access = ($appUser->access_type & Oauth_application::$writeAccess) ? self::READ_WRITE : self::READ_ONLY; @@ -151,38 +160,34 @@ class ApiAuthAction extends ApiAction } $msg = "API OAuth authentication for user '%s' (id: %d) on behalf of " . - "application '%s' (id: %d)."; + "application '%s' (id: %d) with %s access."; common_log(LOG_INFO, sprintf($msg, $this->auth_user->nickname, $this->auth_user->id, $app->name, - $app->id)); + $app->id, + ($this->access = self::READ_WRITE) ? + 'read-write' : 'read-only' + )); return true; } else { throw new OAuthException('Bad access token.'); } } else { - // also should not happen + // Also should not happen + throw new OAuthException('No user for that token.'); - } + } } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); - common_debug(var_export($req, true)); - $this->showOAuthError($e->getMessage()); - exit(); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); + $this->showAuthError(); + exit; } } - function showOAuthError($msg) - { - header('HTTP/1.1 401 Unauthorized'); - header('Content-Type: text/html; charset=utf-8'); - print $msg . "\n"; - } - /** * Does this API resource require authentication? * @@ -207,39 +212,44 @@ class ApiAuthAction extends ApiAction $realm = common_config('site', 'name') . ' API'; - if (!isset($this->auth_user)) { + if (!isset($this->auth_user_nickname)) { header('WWW-Authenticate: Basic realm="' . $realm . '"'); // show error if the user clicks 'cancel' - $this->showBasicAuthError(); + $this->showAuthError(); exit; } else { - $nickname = $this->auth_user; - $password = $this->auth_pw; - $user = common_check_user($nickname, $password); + + $user = common_check_user($this->auth_user_nickname, + $this->auth_user_password); + if (Event::handle('StartSetApiUser', array(&$user))) { $this->auth_user = $user; Event::handle('EndSetApiUser', array($user)); } + // By default, basic auth users have rw access + + $this->access = self::READ_WRITE; + if (empty($this->auth_user)) { // basic authentication failed list($proxy, $ip) = common_client_ip(); + common_log( LOG_WARNING, 'Failed API auth attempt, nickname = ' . "$nickname, proxy = $proxy, ip = $ip." ); - $this->showBasicAuthError(); + + $this->showAuthError(); exit; } } - - return true; } /** @@ -253,32 +263,30 @@ class ApiAuthAction extends ApiAction { if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION']) - ) { - $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) - ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + ) { + $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION']) + ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; } if (isset($_SERVER['PHP_AUTH_USER'])) { - $this->auth_user = $_SERVER['PHP_AUTH_USER']; - $this->auth_pw = $_SERVER['PHP_AUTH_PW']; + $this->auth_user_nickname = $_SERVER['PHP_AUTH_USER']; + $this->auth_user_password = $_SERVER['PHP_AUTH_PW']; } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) { - // decode the HTTP_AUTHORIZATION header on php-cgi server self + // Decode the HTTP_AUTHORIZATION header on php-cgi server self // on fcgid server the header name is AUTHORIZATION $auth_hash = base64_decode(substr($authorization_header, 6)); - list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); + list($this->auth_user_nickname, + $this->auth_user_password) = explode(':', $auth_hash); - // set all to null on a empty basic auth request + // Set all to null on a empty basic auth request - if ($this->auth_user == "") { - $this->auth_user = null; - $this->auth_pw = null; + if (empty($this->auth_user_nickname)) { + $this->auth_user_nickname = null; + $this->auth_password = null; } - } else { - $this->auth_user = null; - $this->auth_pw = null; } } @@ -289,7 +297,7 @@ class ApiAuthAction extends ApiAction * @return void */ - function showBasicAuthError() + function showAuthError() { header('HTTP/1.1 401 Unauthorized'); $msg = 'Could not authenticate you.'; From 756da7bc5174f58f714c858f0100f04b3561a250 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 08:45:56 +0000 Subject: [PATCH 107/130] s/LOG_WARN/LOG_WARNING/ --- actions/apioauthaccesstoken.php | 2 +- actions/apioauthrequesttoken.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/apioauthaccesstoken.php b/actions/apioauthaccesstoken.php index 085ef6f0b1..887df4c20d 100644 --- a/actions/apioauthaccesstoken.php +++ b/actions/apioauthaccesstoken.php @@ -70,7 +70,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction $atok = $server->fetch_access_token($req); } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); common_debug(var_export($req, true)); $this->outputError($e->getMessage()); return; diff --git a/actions/apioauthrequesttoken.php b/actions/apioauthrequesttoken.php index 467640b9aa..4fa626d866 100644 --- a/actions/apioauthrequesttoken.php +++ b/actions/apioauthrequesttoken.php @@ -89,7 +89,7 @@ class ApiOauthRequestTokenAction extends ApiOauthAction $token = $server->fetch_request_token($req); print $token; } catch (OAuthException $e) { - common_log(LOG_WARN, 'API OAuthException - ' . $e->getMessage()); + common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage()); header('HTTP/1.1 401 Unauthorized'); header('Content-Type: text/html; charset=utf-8'); print $e->getMessage() . "\n"; From 54d04a0c911269036a1fc3708fec6d70c33d5032 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 27 Jan 2010 09:59:40 +0000 Subject: [PATCH 108/130] Test script to update your status via OAuth --- tests/oauth/statusupdate.php | 115 +++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/oauth/statusupdate.php diff --git a/tests/oauth/statusupdate.php b/tests/oauth/statusupdate.php new file mode 100644 index 0000000000..4aa230e280 --- /dev/null +++ b/tests/oauth/statusupdate.php @@ -0,0 +1,115 @@ +#!/usr/bin/env php +. + **/ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +require_once INSTALLDIR . '/extlib/OAuth.php'; + +$shortoptions = 'o:s:u:'; +$longoptions = array('oauth_token=', 'token_secret=', 'update='); + +$helptext = <<sign_request($hmac_method, $test_consumer, $at); + +$r = httpRequest($req_req->to_url()); + +$body = $r->getBody(); + +print "$body\n"; + +//print $req_req->to_url() . "\n\n"; + +function httpRequest($url) +{ + $request = HTTPClient::start(); + + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verify_peer' => false, + 'ssl_verify_host' => false + )); + + return $request->post($url); +} + From 04a37fa1c703955a51862033d83e59836c35d74f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:08:33 +0000 Subject: [PATCH 109/130] Better alignment for notice in shownotice page --- theme/base/css/display.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 84e9426c77..65dd159900 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1039,12 +1039,13 @@ overflow:visible; #showstream .notice div.entry-content { margin-left:0; } -#shownotice .notice .entry-title, -#shownotice .notice div.entry-content { -margin-left:110px; -} #shownotice .notice .entry-title { +margin-left:110px; font-size:2.2em; +min-height:123px; +} +#shownotice .notice div.entry-content { +margin-left:0; } .notice p.entry-content { From c52951cef5508452733f84dec7815daf4aca1016 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 11:37:22 -0500 Subject: [PATCH 110/130] Optionally set a separate Javascript server and path We have about 10-12 JavaScript pages per Web page. They usually are based on the same server as the Web pages, but since they're static files, it makes sense to offload them to a lite server that handles static files well. This commit lets you set a separate Javascript server and path for the default Javascript code in StatusNet. Squashed commit of the following: commit 139d1622fdafe5ad00c820224416d9021efc3234 Author: Evan Prodromou Date: Wed Jan 27 11:30:24 2010 -0500 modules that call htmloutputter::script() don't prescribe js/ path commit c6ca3174af73efed55eaed5ff1e2a3bdc77d2d87 Author: Evan Prodromou Date: Wed Jan 27 11:28:07 2010 -0500 configurable server and path for javascript files --- actions/avatarsettings.php | 4 ++-- actions/designadminpanel.php | 4 ++-- actions/grouplogo.php | 4 ++-- lib/action.php | 16 ++++++++-------- lib/default.php | 3 +++ lib/designsettings.php | 4 ++-- lib/htmloutputter.php | 28 +++++++++++++++++++++++++++- plugins/Facebook/facebookaction.php | 2 +- 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index cf45255520..6a7398746a 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -416,8 +416,8 @@ class AvatarsettingsAction extends AccountSettingsAction parent::showScripts(); if ($this->mode == 'crop') { - $this->script('js/jcrop/jquery.Jcrop.min.js'); - $this->script('js/jcrop/jquery.Jcrop.go.js'); + $this->script('jcrop/jquery.Jcrop.min.js'); + $this->script('jcrop/jquery.Jcrop.go.js'); } $this->autofocus('avatarfile'); diff --git a/actions/designadminpanel.php b/actions/designadminpanel.php index 72ad6ade2a..30e8bde1a4 100644 --- a/actions/designadminpanel.php +++ b/actions/designadminpanel.php @@ -302,8 +302,8 @@ class DesignadminpanelAction extends AdminPanelAction { parent::showScripts(); - $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/userdesign.go.js'); + $this->script('farbtastic/farbtastic.js'); + $this->script('userdesign.go.js'); $this->autofocus('design_background-image_file'); } diff --git a/actions/grouplogo.php b/actions/grouplogo.php index f197aef33e..3c9b562962 100644 --- a/actions/grouplogo.php +++ b/actions/grouplogo.php @@ -437,8 +437,8 @@ class GrouplogoAction extends GroupDesignAction parent::showScripts(); if ($this->mode == 'crop') { - $this->script('js/jcrop/jquery.Jcrop.min.js'); - $this->script('js/jcrop/jquery.Jcrop.go.js'); + $this->script('jcrop/jquery.Jcrop.min.js'); + $this->script('jcrop/jquery.Jcrop.go.js'); } $this->autofocus('avatarfile'); diff --git a/lib/action.php b/lib/action.php index 3ffc452dec..cc4f4aad07 100644 --- a/lib/action.php +++ b/lib/action.php @@ -246,18 +246,18 @@ class Action extends HTMLOutputter // lawsuit { if (Event::handle('StartShowScripts', array($this))) { if (Event::handle('StartShowJQueryScripts', array($this))) { - $this->script('js/jquery.min.js'); - $this->script('js/jquery.form.js'); - $this->script('js/jquery.cookie.js'); - $this->script('js/json2.js'); - $this->script('js/jquery.joverlay.min.js'); + $this->script('jquery.min.js'); + $this->script('jquery.form.js'); + $this->script('jquery.cookie.js'); + $this->script('json2.js'); + $this->script('jquery.joverlay.min.js'); Event::handle('EndShowJQueryScripts', array($this)); } if (Event::handle('StartShowStatusNetScripts', array($this)) && Event::handle('StartShowLaconicaScripts', array($this))) { - $this->script('js/xbImportNode.js'); - $this->script('js/util.js'); - $this->script('js/geometa.js'); + $this->script('xbImportNode.js'); + $this->script('util.js'); + $this->script('geometa.js'); // Frame-busting code to avoid clickjacking attacks. $this->element('script', array('type' => 'text/javascript'), 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }'); diff --git a/lib/default.php b/lib/default.php index 37fb65b533..10ea348640 100644 --- a/lib/default.php +++ b/lib/default.php @@ -120,6 +120,9 @@ $default = array('server' => null, 'dir' => null, 'path'=> null), + 'javascript' => + array('server' => null, + 'path'=> null), 'throttle' => array('enabled' => false, // whether to throttle edits; false by default 'count' => 20, // number of allowed messages in timespan diff --git a/lib/designsettings.php b/lib/designsettings.php index 8e44c03a92..4955e92199 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -327,8 +327,8 @@ class DesignSettingsAction extends AccountSettingsAction { parent::showScripts(); - $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/userdesign.go.js'); + $this->script('farbtastic/farbtastic.js'); + $this->script('userdesign.go.js'); $this->autofocus('design_background-image_file'); } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 31660ce954..317f5ea612 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -351,14 +351,40 @@ class HTMLOutputter extends XMLOutputter function script($src, $type='text/javascript') { if(Event::handle('StartScriptElement', array($this,&$src,&$type))) { + $url = parse_url($src); + if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) { - $src = common_path($src) . '?version=' . STATUSNET_VERSION; + $path = common_config('javascript', 'path'); + + if (empty($path)) { + $path = common_config('site', 'path') . '/js/'; + } + + if ($path[strlen($path)-1] != '/') { + $path .= '/'; + } + + if ($path[0] != '/') { + $path = '/'.$path; + } + + $server = common_config('javascript', 'server'); + + if (empty($server)) { + $server = common_config('site', 'server'); + } + + // XXX: protocol + + $src = 'http://'.$server.$path.$src . '?version=' . STATUSNET_VERSION; } + $this->element('script', array('type' => $type, 'src' => $src), ' '); + Event::handle('EndScriptElement', array($this,$src,$type)); } } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index 815fee094c..389e1ea81f 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -89,7 +89,7 @@ class FacebookAction extends Action function showScripts() { - $this->script('js/facebookapp.js'); + $this->script('facebookapp.js'); } /** From 7aeb03f7277de247a642b613775383480bccc63f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 08:53:55 -0800 Subject: [PATCH 111/130] quick fix: use common_path() on realtime update JS so it works with the new JS path code (will pull from main server for now) --- plugins/Realtime/RealtimePlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 89640f5beb..16e28e94d3 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -87,7 +87,7 @@ class RealtimePlugin extends Plugin $scripts = $this->_getScripts(); foreach ($scripts as $script) { - $action->script($script); + $action->script(common_path($script)); } $user = common_current_user(); From c51539804a7f6f63b3f47fd838b9527262440e3d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 09:24:59 -0800 Subject: [PATCH 112/130] Add persistent:true property to Stomp messages so ActiveMQ doesn't decide to discard them even though persistence is enabled on the broker. :) (Thanks Aric!) --- lib/stompqueuemanager.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 89f3d74cce..19e8c49b5c 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -178,7 +178,8 @@ class StompQueueManager extends QueueManager $result = $this->con->send($this->queueName($queue), $msg, // BODY of the message - array ('created' => common_sql_now())); + array ('created' => common_sql_now(), + 'persistent' => 'true')); if (!$result) { common_log(LOG_ERR, "Error sending $rep to $queue queue"); From 61a7a7b36b4f4b504dde880695dff5113dbe2a48 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 18:18:32 +0100 Subject: [PATCH 113/130] Plugin for Universal Ad Package. Outputs four most widely used ad types. --- plugins/UAP/UAPPlugin.php | 176 ++++++++++++++++++++++++++++++++++++++ plugins/UAP/uap.css | 50 +++++++++++ 2 files changed, 226 insertions(+) create mode 100644 plugins/UAP/UAPPlugin.php create mode 100644 plugins/UAP/uap.css diff --git a/plugins/UAP/UAPPlugin.php b/plugins/UAP/UAPPlugin.php new file mode 100644 index 0000000000..13c1017230 --- /dev/null +++ b/plugins/UAP/UAPPlugin.php @@ -0,0 +1,176 @@ +. + * + * @category Action + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Outputs the following ad types (based on UAP): + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * Any number of ad types can be used. Enable all using example: + * addPlugin('UAP', array( + * 'MediumRectangle' => '', + * 'Rectangle' => '', + * 'Leaderboard' => '', + * 'WideSkyscraper' => '' + * ) + * ); + * + * @category Plugin + * @package StatusNet + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class UAPPlugin extends Plugin +{ + public $MediumRectangle = null; + public $Rectangle = null; + public $Leaderboard = null; + public $WideSkyscraper = null; + + function __construct($uap = array()) + { + $this->uap = $uap; + + parent::__construct(); + } + + function onInitializePlugin() + { + foreach($this->uap as $key => $value) { + switch(strtolower($key)) { + case 'MediumRectangle': default: + $this->MediumRectangle = $value; + break; + case 'Rectangle': + $this->Rectangle = $value; + break; + case 'Leaderboard': + $this->Leaderboard = $value; + break; + case 'WideSkyscraper': + $this->WideSkyscraper = $value; + break; + } + } + } + + function onEndShowStatusNetStyles($action) + { + $action->cssLink(common_path('plugins/UAP/uap.css'), + null, 'screen, projection, tv'); + return true; + } + + //MediumRectangle ad + function onStartShowAside($action) + { + if (!$this->MediumRectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_medium-rectangle'), + $this->MediumRectangle); + + return true; + } + +/* + //Rectangle ad + function onEndShowSiteNotice($action) + { + if (!$this->Rectangle) { + return true; + } + + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + + return true; + } +*/ + + //Leaderboard and Rectangle ad + function onStartShowHeader($action) + { + if ($this->Leaderboard) { + $this->showAd($action, array('id' => 'ad_leaderboard'), + $this->Leaderboard); + } + + if ($this->Rectangle) { + $this->showAd($action, array('id' => 'ad_rectangle'), + $this->Rectangle); + } + + return true; + } + + //WideSkyscraper ad + function onEndShowAside($action) + { + if (!$this->WideSkyscraper) { + return true; + } + + $this->showAd($action, array('id' => 'ad_wide-skyscraper'), + $this->WideSkyscraper); + + return true; + } + + //Output ad container + function showAd($action, $attr=array(), $value) + { + $classes = ($attr['class']) ? $attr['class'].' ' : ''; + + $action->elementStart('div', array('id' => $attr['id'], + 'class' => $classes.'ad')); + $action->raw($value); + $action->elementEnd('div'); + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'UAP', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:UAP', + 'rawdescription' => + _m('Outputs ad placements based on Universal Ad Package')); + return true; + } +} diff --git a/plugins/UAP/uap.css b/plugins/UAP/uap.css new file mode 100644 index 0000000000..90abe51807 --- /dev/null +++ b/plugins/UAP/uap.css @@ -0,0 +1,50 @@ +/** Universal Ad Package styles: + * Medium Rectangle 300x250 + * Rectangle 180x150 + * Leaderboard 728x90 + * Wide Skyscraper 160x600 + * + * @package StatusNet + * @author Sarven Capadisli + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + + +.ad { +border:1px solid #CCC; +float:left; +} + +#ad_medium-rectangle { +width:300px; +height:250px; + +margin-left:1.35%; +margin-bottom:18px; +} + +#ad_rectangle { +width:180px; +height:150px; + +float:right; +margin-right:18px; +} + +#ad_leaderboard { +width:728px; +height:90px; + +margin:29px 18px 0 0; +clear:both; +} + +#ad_wide-skyscraper { +width:160px; +height:600px; + +margin-top:18px; +margin-left:8.5%; +} From 58fde0dcb5342cb3d3ff44c2e54a077eea0e063e Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Thu, 21 Jan 2010 18:28:02 +0100 Subject: [PATCH 114/130] Lowercased switch cases in UAP Plugin --- plugins/UAP/UAPPlugin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/UAP/UAPPlugin.php b/plugins/UAP/UAPPlugin.php index 13c1017230..f3b16548d2 100644 --- a/plugins/UAP/UAPPlugin.php +++ b/plugins/UAP/UAPPlugin.php @@ -72,16 +72,16 @@ class UAPPlugin extends Plugin { foreach($this->uap as $key => $value) { switch(strtolower($key)) { - case 'MediumRectangle': default: + case 'mediumrectangle': default: $this->MediumRectangle = $value; break; - case 'Rectangle': + case 'rectangle': $this->Rectangle = $value; break; - case 'Leaderboard': + case 'leaderboard': $this->Leaderboard = $value; break; - case 'WideSkyscraper': + case 'wideskyscraper': $this->WideSkyscraper = $value; break; } From 1758ed453bcf2abd11bc2fde8768632099fe0de3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 06:25:22 -0500 Subject: [PATCH 115/130] move UAP plugin to core --- plugins/UAP/UAPPlugin.php => lib/uapplugin.php | 0 {plugins/UAP => theme/base/css}/uap.css | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename plugins/UAP/UAPPlugin.php => lib/uapplugin.php (100%) rename {plugins/UAP => theme/base/css}/uap.css (100%) diff --git a/plugins/UAP/UAPPlugin.php b/lib/uapplugin.php similarity index 100% rename from plugins/UAP/UAPPlugin.php rename to lib/uapplugin.php diff --git a/plugins/UAP/uap.css b/theme/base/css/uap.css similarity index 100% rename from plugins/UAP/uap.css rename to theme/base/css/uap.css From 7c54591472b4ff997ea099792c95d40b98798381 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:19:36 -0500 Subject: [PATCH 116/130] make uapplugin an abstract class --- lib/uapplugin.php | 199 ++++++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 94 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index f3b16548d2..5160d96629 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -22,6 +22,7 @@ * @category Action * @package StatusNet * @author Sarven Capadisli + * @author Evan Prodromou * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -32,145 +33,155 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { } /** + * Abstract superclass for advertising plugins + * + * Plugins for showing ads should derive from this plugin. + * * Outputs the following ad types (based on UAP): + * * Medium Rectangle 300x250 * Rectangle 180x150 * Leaderboard 728x90 * Wide Skyscraper 160x600 * - * Any number of ad types can be used. Enable all using example: - * addPlugin('UAP', array( - * 'MediumRectangle' => '', - * 'Rectangle' => '', - * 'Leaderboard' => '', - * 'WideSkyscraper' => '' - * ) - * ); - * * @category Plugin * @package StatusNet * @author Sarven Capadisli + * @author Evan Prodromou * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class UAPPlugin extends Plugin +abstract class UAPPlugin extends Plugin { public $MediumRectangle = null; - public $Rectangle = null; - public $Leaderboard = null; - public $WideSkyscraper = null; + public $Rectangle = null; + public $Leaderboard = null; + public $WideSkyscraper = null; - function __construct($uap = array()) - { - $this->uap = $uap; - - parent::__construct(); - } - - function onInitializePlugin() - { - foreach($this->uap as $key => $value) { - switch(strtolower($key)) { - case 'mediumrectangle': default: - $this->MediumRectangle = $value; - break; - case 'rectangle': - $this->Rectangle = $value; - break; - case 'leaderboard': - $this->Leaderboard = $value; - break; - case 'wideskyscraper': - $this->WideSkyscraper = $value; - break; - } - } - } + /** + * Output our dedicated stylesheet + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ function onEndShowStatusNetStyles($action) { - $action->cssLink(common_path('plugins/UAP/uap.css'), - null, 'screen, projection, tv'); + // XXX: allow override by theme + $action->cssLink('css/uap.css', 'base', 'screen, projection, tv'); return true; } - //MediumRectangle ad + /** + * Add a medium rectangle ad at the beginning of sidebar + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + function onStartShowAside($action) { - if (!$this->MediumRectangle) { - return true; - } + if (!is_null($this->mediumRectangle)) { - $this->showAd($action, array('id' => 'ad_medium-rectangle'), - $this->MediumRectangle); + $action->elementStart('div', + array('class' => 'ad_medium-rectangle ad')); + + $this->showMediumRectangle($action); + + $action->elementEnd('div'); + } return true; } -/* - //Rectangle ad - function onEndShowSiteNotice($action) - { - if (!$this->Rectangle) { - return true; - } + /** + * Add a leaderboard and/or rectangle in the header + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); - - return true; - } -*/ - - //Leaderboard and Rectangle ad function onStartShowHeader($action) { - if ($this->Leaderboard) { - $this->showAd($action, array('id' => 'ad_leaderboard'), - $this->Leaderboard); + if (!is_null($this->leaderboard)) { + $action->elementStart('div', + array('class' => 'ad_leaderboard ad')); + $this->showLeaderboard($action); + $action->elementEnd('div'); } - if ($this->Rectangle) { - $this->showAd($action, array('id' => 'ad_rectangle'), - $this->Rectangle); + if (!is_null($this->rectangle)) { + $action->elementStart('div', + array('class' => 'ad_rectangle ad')); + $this->showRectangle($action); + $action->elementEnd('div'); } return true; } - //WideSkyscraper ad + /** + * Add a wide skyscraper after the aside + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + function onEndShowAside($action) { - if (!$this->WideSkyscraper) { - return true; + if (!is_null($this->wideSkyscraper)) { + $action->elementStart('div', + array('class' => 'ad_wide-skyscraper ad')); + + $this->showWideSkyscraper($action); + + $action->elementEnd('div'); } - - $this->showAd($action, array('id' => 'ad_wide-skyscraper'), - $this->WideSkyscraper); - return true; } - //Output ad container - function showAd($action, $attr=array(), $value) - { - $classes = ($attr['class']) ? $attr['class'].' ' : ''; + /** + * Show a medium rectangle ad + * + * @param Action $action Action being shown + * + * @return void + */ - $action->elementStart('div', array('id' => $attr['id'], - 'class' => $classes.'ad')); - $action->raw($value); - $action->elementEnd('div'); - } + abstract protected function showMediumRectangle($action); - function onPluginVersion(&$versions) - { - $versions[] = array('name' => 'UAP', - 'version' => STATUSNET_VERSION, - 'author' => 'Sarven Capadisli', - 'homepage' => 'http://status.net/wiki/Plugin:UAP', - 'rawdescription' => - _m('Outputs ad placements based on Universal Ad Package')); - return true; - } + /** + * Show a rectangle ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showRectangle($action); + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showWideSkyscraper($action); + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + abstract protected function showLeaderboard($action); } From 9decd9806c675ce820a1443507846ba9c57ec72b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:26:32 -0500 Subject: [PATCH 117/130] Add BlankAdPlugin to test ad layout in different themes --- plugins/BlankAdPlugin.php | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 plugins/BlankAdPlugin.php diff --git a/plugins/BlankAdPlugin.php b/plugins/BlankAdPlugin.php new file mode 100644 index 0000000000..bf3a225c01 --- /dev/null +++ b/plugins/BlankAdPlugin.php @@ -0,0 +1,121 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin for testing ad layout + * + * This plugin uses the UAPPlugin framework to output ad content. However, + * its ad content is just paragraphs with defined background colors. It's + * mostly useful for debugging theme layout. + * + * To use this plugin, set the parameter for the ad size you want to use + * to the background you want to use. For example, to make a leaderboard + * that's red: + * + * addPlugin('BlankAd', array('leaderboard' => 'red')); + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso Location + */ + +class BlankAdPlugin extends UAPPlugin +{ + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $style = 'width: 300px; height: 250px; background-color: ' . + $this->mediumRectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $style = 'width: 180px; height: 150px; background-color: ' . + $this->rectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $style = 'width: 160px; height: 600px; background-color: ' . + $this->wideSkyscraper; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $style = 'width: 728px; height: 90px; background-color: ' . + $this->leaderboard; + + $action->element('p', array('style' => $style), ''); + } +} \ No newline at end of file From b412ebab11e4ef55bc06b6ea5dfe11fd66614f50 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:34:31 -0500 Subject: [PATCH 118/130] move BlankAdPlugin to its own dir --- plugins/BlankAd/BlankAdPlugin.php | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 plugins/BlankAd/BlankAdPlugin.php diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php new file mode 100644 index 0000000000..bf3a225c01 --- /dev/null +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -0,0 +1,121 @@ +. + * + * @category Ads + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin for testing ad layout + * + * This plugin uses the UAPPlugin framework to output ad content. However, + * its ad content is just paragraphs with defined background colors. It's + * mostly useful for debugging theme layout. + * + * To use this plugin, set the parameter for the ad size you want to use + * to the background you want to use. For example, to make a leaderboard + * that's red: + * + * addPlugin('BlankAd', array('leaderboard' => 'red')); + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @seeAlso Location + */ + +class BlankAdPlugin extends UAPPlugin +{ + /** + * Show a medium rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showMediumRectangle($action) + { + $style = 'width: 300px; height: 250px; background-color: ' . + $this->mediumRectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a rectangle 'ad' + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showRectangle($action) + { + $style = 'width: 180px; height: 150px; background-color: ' . + $this->rectangle; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a wide skyscraper ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showWideSkyscraper($action) + { + $style = 'width: 160px; height: 600px; background-color: ' . + $this->wideSkyscraper; + + $action->element('p', array('style' => $style), ''); + } + + /** + * Show a leaderboard ad + * + * @param Action $action Action being shown + * + * @return void + */ + + protected function showLeaderboard($action) + { + $style = 'width: 728px; height: 90px; background-color: ' . + $this->leaderboard; + + $action->element('p', array('style' => $style), ''); + } +} \ No newline at end of file From b2b95bd21fa3995b77af5096da80d52bf6938c3b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:41:12 -0500 Subject: [PATCH 119/130] make BlankAd dir and change to use a 1x1 image --- plugins/BlankAd/redpixel.png | Bin 0 -> 159 bytes plugins/BlankAdPlugin.php | 121 ----------------------------------- 2 files changed, 121 deletions(-) create mode 100644 plugins/BlankAd/redpixel.png delete mode 100644 plugins/BlankAdPlugin.php diff --git a/plugins/BlankAd/redpixel.png b/plugins/BlankAd/redpixel.png new file mode 100644 index 0000000000000000000000000000000000000000..26299a552574ed7b95a6eba5270067f44ad765eb GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?slj7I;J!Gca%q zgD@k*tT_@uLG}_)Usv{9jM6-cJf2o(vVlU9C9V-A&iT2ysd*&~&PAz-C8;S2<(VZJ w3hti10pX2&;y^__o-U3d9M_Y7oIk+8z|6?VAaS>M15lj7)78&qol`;+06pa;zW@LL literal 0 HcmV?d00001 diff --git a/plugins/BlankAdPlugin.php b/plugins/BlankAdPlugin.php deleted file mode 100644 index bf3a225c01..0000000000 --- a/plugins/BlankAdPlugin.php +++ /dev/null @@ -1,121 +0,0 @@ -. - * - * @category Ads - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -/** - * Plugin for testing ad layout - * - * This plugin uses the UAPPlugin framework to output ad content. However, - * its ad content is just paragraphs with defined background colors. It's - * mostly useful for debugging theme layout. - * - * To use this plugin, set the parameter for the ad size you want to use - * to the background you want to use. For example, to make a leaderboard - * that's red: - * - * addPlugin('BlankAd', array('leaderboard' => 'red')); - * - * @category Plugin - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - * @seeAlso Location - */ - -class BlankAdPlugin extends UAPPlugin -{ - /** - * Show a medium rectangle 'ad' - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showMediumRectangle($action) - { - $style = 'width: 300px; height: 250px; background-color: ' . - $this->mediumRectangle; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a rectangle 'ad' - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showRectangle($action) - { - $style = 'width: 180px; height: 150px; background-color: ' . - $this->rectangle; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a wide skyscraper ad - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showWideSkyscraper($action) - { - $style = 'width: 160px; height: 600px; background-color: ' . - $this->wideSkyscraper; - - $action->element('p', array('style' => $style), ''); - } - - /** - * Show a leaderboard ad - * - * @param Action $action Action being shown - * - * @return void - */ - - protected function showLeaderboard($action) - { - $style = 'width: 728px; height: 90px; background-color: ' . - $this->leaderboard; - - $action->element('p', array('style' => $style), ''); - } -} \ No newline at end of file From e4393ee6dbcfbcf660d898dd1eb5fe91c4e3b80d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 07:51:25 -0500 Subject: [PATCH 120/130] Add the moved BlankAdPlugin --- plugins/BlankAd/BlankAdPlugin.php | 45 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php index bf3a225c01..5f46ddd48d 100644 --- a/plugins/BlankAd/BlankAdPlugin.php +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -35,14 +35,13 @@ if (!defined('STATUSNET')) { * Plugin for testing ad layout * * This plugin uses the UAPPlugin framework to output ad content. However, - * its ad content is just paragraphs with defined background colors. It's - * mostly useful for debugging theme layout. + * its ad content is just images with one red pixel stretched to the + * right size. It's mostly useful for debugging theme layout. * * To use this plugin, set the parameter for the ad size you want to use - * to the background you want to use. For example, to make a leaderboard - * that's red: + * to true (or anything non-null). For example, to make a leaderboard: * - * addPlugin('BlankAd', array('leaderboard' => 'red')); + * addPlugin('BlankAd', array('leaderboard' => true)); * * @category Plugin * @package StatusNet @@ -65,10 +64,11 @@ class BlankAdPlugin extends UAPPlugin protected function showMediumRectangle($action) { - $style = 'width: 300px; height: 250px; background-color: ' . - $this->mediumRectangle; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 300, + 'height' => 250, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -81,10 +81,11 @@ class BlankAdPlugin extends UAPPlugin protected function showRectangle($action) { - $style = 'width: 180px; height: 150px; background-color: ' . - $this->rectangle; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 180, + 'height' => 50, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -97,10 +98,11 @@ class BlankAdPlugin extends UAPPlugin protected function showWideSkyscraper($action) { - $style = 'width: 160px; height: 600px; background-color: ' . - $this->wideSkyscraper; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 160, + 'height' => 600, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } /** @@ -113,9 +115,10 @@ class BlankAdPlugin extends UAPPlugin protected function showLeaderboard($action) { - $style = 'width: 728px; height: 90px; background-color: ' . - $this->leaderboard; - - $action->element('p', array('style' => $style), ''); + $action->element('img', + array('width' => 728, + 'height' => 90, + 'src' => common_path('plugins/BlankAd/redpixel.png')), + ''); } } \ No newline at end of file From 4ad931ad38f1eba1f5b00a923854544dcb4aa0d6 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 08:13:05 -0500 Subject: [PATCH 121/130] wrong height for rectangle in BlankAd --- plugins/BlankAd/BlankAdPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/BlankAd/BlankAdPlugin.php b/plugins/BlankAd/BlankAdPlugin.php index 5f46ddd48d..0e2719aed0 100644 --- a/plugins/BlankAd/BlankAdPlugin.php +++ b/plugins/BlankAd/BlankAdPlugin.php @@ -83,7 +83,7 @@ class BlankAdPlugin extends UAPPlugin { $action->element('img', array('width' => 180, - 'height' => 50, + 'height' => 150, 'src' => common_path('plugins/BlankAd/redpixel.png')), ''); } From e9feafc3ca4641bf5c51bbced3454962ed89b8ef Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 08:13:24 -0500 Subject: [PATCH 122/130] CSS ids and classes fixed in UAPPlugin --- lib/uapplugin.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 5160d96629..4364b1e190 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -87,7 +87,8 @@ abstract class UAPPlugin extends Plugin if (!is_null($this->mediumRectangle)) { $action->elementStart('div', - array('class' => 'ad_medium-rectangle ad')); + array('id' => 'ad_medium-rectangle', + 'class' => 'ad')); $this->showMediumRectangle($action); @@ -109,14 +110,16 @@ abstract class UAPPlugin extends Plugin { if (!is_null($this->leaderboard)) { $action->elementStart('div', - array('class' => 'ad_leaderboard ad')); + array('id' => 'ad_leaderboard', + 'class' => 'ad')); $this->showLeaderboard($action); $action->elementEnd('div'); } if (!is_null($this->rectangle)) { $action->elementStart('div', - array('class' => 'ad_rectangle ad')); + array('id' => 'ad_rectangle', + 'class' => 'ad')); $this->showRectangle($action); $action->elementEnd('div'); } @@ -136,7 +139,8 @@ abstract class UAPPlugin extends Plugin { if (!is_null($this->wideSkyscraper)) { $action->elementStart('div', - array('class' => 'ad_wide-skyscraper ad')); + array('id' => 'ad_wide-skyscraper', + 'class' => 'ad')); $this->showWideSkyscraper($action); From e3bd97bfe46ee7d063a45eec282b28faee1ca83f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:14:00 +0100 Subject: [PATCH 123/130] Aligning wide skyscraper to the right instead of left --- theme/base/css/uap.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index 90abe51807..b95e9b2ce4 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -45,6 +45,7 @@ clear:both; width:160px; height:600px; +float:right; margin-top:18px; -margin-left:8.5%; +margin-right:8.25%; } From 1c875a53952506ca95716992511421d110a37d4d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 15:32:59 +0100 Subject: [PATCH 124/130] Moved rectangle ad into aside and leaderboard to the right in header. Intention for this layout was to reduce whitespace in header area --- lib/uapplugin.php | 15 ++++++++++++++- theme/base/css/uap.css | 9 ++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 4364b1e190..69b68f9367 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -99,7 +99,7 @@ abstract class UAPPlugin extends Plugin } /** - * Add a leaderboard and/or rectangle in the header + * Add a leaderboard in the header * * @param Action $action Action being shown * @@ -116,6 +116,19 @@ abstract class UAPPlugin extends Plugin $action->elementEnd('div'); } + return true; + } + + /** + * Add a rectangle before aside sections + * + * @param Action $action Action being shown + * + * @return boolean hook flag + */ + + function onStartShowSections($action) + { if (!is_null($this->rectangle)) { $action->elementStart('div', array('id' => 'ad_rectangle', diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index b95e9b2ce4..d7fa02c7f9 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -29,15 +29,18 @@ margin-bottom:18px; width:180px; height:150px; -float:right; -margin-right:18px; +float:none; +clear:both; +margin:0 auto; +margin-bottom:29px; } #ad_leaderboard { width:728px; height:90px; -margin:29px 18px 0 0; +margin:11px 18px 0 0; +float:right; clear:both; } From ea123800e991352d862c05a2f81e82ef53e72eeb Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 10:44:49 -0500 Subject: [PATCH 125/130] move leaderboard to after the header --- lib/uapplugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index 69b68f9367..e3801f08ae 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -106,7 +106,7 @@ abstract class UAPPlugin extends Plugin * @return boolean hook flag */ - function onStartShowHeader($action) + function onEndShowHeader($action) { if (!is_null($this->leaderboard)) { $action->elementStart('div', From 760be76fc000c546ac38e96fa1cc5f822b7e6993 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 27 Jan 2010 10:45:44 -0500 Subject: [PATCH 126/130] camelcase the uap param names --- lib/uapplugin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/uapplugin.php b/lib/uapplugin.php index e3801f08ae..ef35bafbfb 100644 --- a/lib/uapplugin.php +++ b/lib/uapplugin.php @@ -54,10 +54,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { abstract class UAPPlugin extends Plugin { - public $MediumRectangle = null; - public $Rectangle = null; - public $Leaderboard = null; - public $WideSkyscraper = null; + public $mediumRectangle = null; + public $rectangle = null; + public $leaderboard = null; + public $wideSkyscraper = null; /** * Output our dedicated stylesheet From 5e31ecd82aaf102382380b1352503303893cf9ba Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 27 Jan 2010 16:52:18 +0100 Subject: [PATCH 127/130] Centred leaderboard ad --- theme/base/css/uap.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/base/css/uap.css b/theme/base/css/uap.css index d7fa02c7f9..73be5f0c14 100644 --- a/theme/base/css/uap.css +++ b/theme/base/css/uap.css @@ -39,8 +39,8 @@ margin-bottom:29px; width:728px; height:90px; -margin:11px 18px 0 0; -float:right; +margin:0 auto 18px; +float:none; clear:both; } From 47645228da4a9af8ffb0dc1f11c1da995f4791cb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 13:58:55 -0800 Subject: [PATCH 128/130] Add new oauth tables and modifications to 'consumer' table for rc4 --- db/rc3to09.sql | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/db/rc3to09.sql b/db/rc3to09.sql index 02dc7a6e2e..8342c4bc6f 100644 --- a/db/rc3to09.sql +++ b/db/rc3to09.sql @@ -14,3 +14,35 @@ insert into queue_item_new (frame,transport,created,claimed) alter table queue_item rename to queue_item_old; alter table queue_item_new rename to queue_item; +alter table consumer + add consumer_secret varchar(255) not null comment 'secret value', + add verifier varchar(255) comment 'verifier string for OAuth 1.0a', + add verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a'; + +create table oauth_application ( + id integer auto_increment primary key comment 'unique identifier', + owner integer not null comment 'owner of the application' references profile (id), + consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key), + name varchar(255) not null comment 'name of the application', + description varchar(255) comment 'description of the application', + icon varchar(255) not null comment 'application icon', + source_url varchar(255) comment 'application homepage - used for source link', + organization varchar(255) comment 'name of the organization running the application', + homepage varchar(255) comment 'homepage for the organization', + callback_url varchar(255) comment 'url to redirect to after authentication', + type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop', + access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified' +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table oauth_application_user ( + profile_id integer not null comment 'user of the application' references profile (id), + application_id integer not null comment 'id of the application' references oauth_application (id), + access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked', + token varchar(255) comment 'request or access token', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + constraint primary key (profile_id, application_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + From 9a54745fcd252e27cc664da7cee67b48d0f4f501 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 27 Jan 2010 13:59:58 -0800 Subject: [PATCH 129/130] Rename rc3to09.sql to rc3torc4.sql to avoid confusion if we add a last-minute change after this! --- db/{rc3to09.sql => rc3torc4.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/{rc3to09.sql => rc3torc4.sql} (100%) diff --git a/db/rc3to09.sql b/db/rc3torc4.sql similarity index 100% rename from db/rc3to09.sql rename to db/rc3torc4.sql From b0a325f7d0418575cdb46b7074c4cd2317f04980 Mon Sep 17 00:00:00 2001 From: Michele Date: Sun, 17 Jan 2010 11:21:07 +0100 Subject: [PATCH 130/130] HTTP auth provided is evaluated even if it's not required --- lib/apiauth.php | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/apiauth.php b/lib/apiauth.php index ad9651ff26..ac5e997c78 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -84,16 +84,22 @@ class ApiAuthAction extends ApiAction } else { $this->checkBasicAuthUser(); } + } else { - // Reject API calls with the wrong access level + // Check to see if a basic auth user is there even + // if one's not required - if ($this->isReadOnly($args) == false) { - if ($this->access != self::READ_WRITE) { - $msg = 'API resource requires read-write access, ' . - 'but you only have read access.'; - $this->clientError($msg, 401, $this->format); - exit(); - } + $this->checkBasicAuthUser(false); + } + + // Reject API calls with the wrong access level + + if ($this->isReadOnly($args) == false) { + if ($this->access != self::READ_WRITE) { + $msg = 'API resource requires read-write access, ' . + 'but you only have read access.'; + $this->clientError($msg, 401, $this->format); + exit; } } @@ -206,13 +212,13 @@ class ApiAuthAction extends ApiAction * @return boolean true or false */ - function checkBasicAuthUser() + function checkBasicAuthUser($required = true) { $this->basicAuthProcessHeader(); $realm = common_config('site', 'name') . ' API'; - if (!isset($this->auth_user_nickname)) { + if (!isset($this->auth_user_nickname) && $required) { header('WWW-Authenticate: Basic realm="' . $realm . '"'); // show error if the user clicks 'cancel' @@ -222,11 +228,10 @@ class ApiAuthAction extends ApiAction } else { - $user = common_check_user($this->auth_user_nickname, - $this->auth_user_password); - if (Event::handle('StartSetApiUser', array(&$user))) { - $this->auth_user = $user; + $this->auth_user = common_check_user($this->auth_user_nickname, + $this->auth_user_password); + Event::handle('EndSetApiUser', array($user)); }