From 0954b3ce74da0f382c725cc44045484e6688f93d Mon Sep 17 00:00:00 2001 From: thisgun Date: Fri, 11 Aug 2017 15:35:18 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=EB=89=B4=EC=96=BC=EA=B3=BC=20?= =?UTF-8?q?=ED=95=A9=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adm/config_form.php | 177 +- adm/config_form_update.php | 14 + adm/css/admin.css | 31 + adm/img/social/sns_logo.png | Bin 0 -> 6045 bytes adm/img/social/sns_logo_not.png | Bin 0 -> 5465 bytes adm/member_form.php | 88 + adm/member_list.php | 27 +- bbs/board.php | 4 +- bbs/login.php | 4 + bbs/login_check.php | 36 +- bbs/logout.php | 4 + bbs/member_confirm.php | 4 + bbs/member_leave.php | 5 + bbs/move.php | 81 +- bbs/register_form.php | 7 + bbs/register_result.php | 2 +- common.php | 4 + config.php | 3 - css/default.css | 318 +- css/mobile.css | 296 +- extend/social_login.extend.php | 32 + img/captcha.png | Bin 19945 -> 3052 bytes img/ft_logo.jpg | Bin 0 -> 3823 bytes img/m_logo.png | Bin 0 -> 2151 bytes img/ts01.png | Bin 0 -> 1219 bytes img/ts02.png | Bin 0 -> 1252 bytes img/ts03.png | Bin 0 -> 1319 bytes install/gnuboard5.sql | 12 + js/common.js | 2 +- js/font-awesome/HELP-US-OUT.txt | 7 + js/font-awesome/css/font-awesome.css | 2337 ++++++++++++++ js/font-awesome/css/font-awesome.min.css | 4 + js/font-awesome/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes js/font-awesome/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes js/font-awesome/fonts/fontawesome-webfont.svg | 2671 +++++++++++++++++ js/font-awesome/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes js/font-awesome/less/animated.less | 34 + js/font-awesome/less/bordered-pulled.less | 25 + js/font-awesome/less/core.less | 12 + js/font-awesome/less/fixed-width.less | 6 + js/font-awesome/less/font-awesome.less | 18 + js/font-awesome/less/icons.less | 789 +++++ js/font-awesome/less/larger.less | 13 + js/font-awesome/less/list.less | 19 + js/font-awesome/less/mixins.less | 60 + js/font-awesome/less/path.less | 15 + js/font-awesome/less/rotated-flipped.less | 20 + js/font-awesome/less/screen-reader.less | 5 + js/font-awesome/less/stacked.less | 20 + js/font-awesome/less/variables.less | 800 +++++ js/font-awesome/scss/_animated.scss | 34 + js/font-awesome/scss/_bordered-pulled.scss | 25 + js/font-awesome/scss/_core.scss | 12 + js/font-awesome/scss/_fixed-width.scss | 6 + js/font-awesome/scss/_icons.scss | 789 +++++ js/font-awesome/scss/_larger.scss | 13 + js/font-awesome/scss/_list.scss | 19 + js/font-awesome/scss/_mixins.scss | 60 + js/font-awesome/scss/_path.scss | 15 + js/font-awesome/scss/_rotated-flipped.scss | 20 + js/font-awesome/scss/_screen-reader.scss | 5 + js/font-awesome/scss/_stacked.scss | 20 + js/font-awesome/scss/_variables.scss | 800 +++++ js/font-awesome/scss/font-awesome.scss | 18 + js/placeholders.min.js | 25 + lib/common.lib.php | 28 +- lib/social_member.lib.php | 51 + mobile/skin/member/basic/login.skin.php | 5 + .../skin/member/basic/register_form.skin.php | 8 + mobile/skin/social/img/sns_logo.png | Bin 0 -> 6045 bytes mobile/skin/social/img/sns_logo_not.png | Bin 0 -> 5465 bytes mobile/skin/social/index.php | 0 .../social/register_social_member.skin.php | 220 ++ mobile/skin/social/social_login_icon.skin.php | 83 + .../skin/social/social_member_form.skin.php | 164 + mobile/skin/social/style.css | 100 + .../editor/smarteditor2/css/smart_editor2.css | 1 + plugin/kcaptcha/kcaptcha.lib.php | 2 +- plugin/recaptcha/_common.php | 3 + plugin/recaptcha/captcha.lib.php | 4 + plugin/recaptcha/recaptcha.class.php | 147 + plugin/recaptcha/recaptcha.js | 9 + plugin/recaptcha/recaptcha.user.lib.php | 51 + plugin/sns/icon/facebook.png | Bin 1052 -> 3123 bytes plugin/sns/icon/gplus.png | Bin 1669 -> 3400 bytes plugin/sns/icon/kakaotalk.png | Bin 1532 -> 4179 bytes plugin/sns/icon/twitter.png | Bin 1030 -> 3393 bytes plugin/sns/view.sns.skin.php | 11 +- plugin/sns/view_comment_write.sns.skin.php | 11 +- plugin/social/Hybrid/Auth.php | 414 +++ plugin/social/Hybrid/Endpoint.php | 222 ++ plugin/social/Hybrid/Error.php | 88 + plugin/social/Hybrid/Exception.php | 17 + plugin/social/Hybrid/Logger.php | 102 + plugin/social/Hybrid/Provider_Adapter.php | 340 +++ plugin/social/Hybrid/Provider_Model.php | 247 ++ .../social/Hybrid/Provider_Model_OAuth1.php | 174 ++ .../social/Hybrid/Provider_Model_OAuth2.php | 184 ++ .../social/Hybrid/Provider_Model_OpenID.php | 170 ++ plugin/social/Hybrid/Providers/Facebook.php | 330 ++ plugin/social/Hybrid/Providers/Google.php | 308 ++ plugin/social/Hybrid/Providers/Kakao.php | 168 ++ plugin/social/Hybrid/Providers/Naver.php | 229 ++ plugin/social/Hybrid/Providers/Payco.php | 382 +++ plugin/social/Hybrid/Providers/Twitter.php | 266 ++ plugin/social/Hybrid/Storage.php | 141 + plugin/social/Hybrid/StorageInterface.php | 29 + plugin/social/Hybrid/User.php | 40 + plugin/social/Hybrid/User_Activity.php | 55 + plugin/social/Hybrid/User_Contact.php | 60 + plugin/social/Hybrid/User_Profile.php | 163 + plugin/social/Hybrid/index.html | 10 + plugin/social/Hybrid/resources/index.html | 10 + .../Hybrid/resources/openid_policy.html | 10 + .../social/Hybrid/resources/openid_realm.html | 13 + .../social/Hybrid/resources/openid_xrds.xml | 12 + .../social/Hybrid/thirdparty/OAuth/OAuth.php | 901 ++++++ .../Hybrid/thirdparty/OAuth/OAuth1Client.php | 264 ++ .../Hybrid/thirdparty/OAuth/OAuth2Client.php | 302 ++ .../Hybrid/thirdparty/OpenID/LightOpenID.php | 1051 +++++++ plugin/social/Hybrid/thirdparty/index.html | 10 + plugin/social/_common.php | 3 + plugin/social/config.php | 62 + plugin/social/error.php | 68 + plugin/social/img/loading_icon.gif | Bin 0 -> 9197 bytes plugin/social/includes/functions.php | 897 ++++++ plugin/social/includes/g5_endpoint.php | 32 + plugin/social/includes/index.php | 0 plugin/social/includes/loading.php | 68 + plugin/social/includes/providers.php | 244 ++ plugin/social/index.php | 17 + plugin/social/popup.php | 43 + plugin/social/register_member.php | 48 + plugin/social/register_member_update.php | 126 + plugin/social/unlink.php | 48 + skin/member/basic/login.skin.php | 5 + skin/member/basic/member_confirm.skin.php | 5 + skin/member/basic/register_form.skin.php | 8 + skin/social/img/sns_logo.png | Bin 0 -> 6045 bytes skin/social/img/sns_logo_not.png | Bin 0 -> 5465 bytes skin/social/index.php | 0 skin/social/register_social_member.skin.php | 219 ++ skin/social/social_login_icon.skin.php | 83 + skin/social/social_member_form.skin.php | 169 ++ skin/social/style.css | 59 + theme/basic/css/default.css | 304 +- theme/basic/css/mobile.css | 289 +- theme/basic/head.php | 227 +- theme/basic/head.sub.php | 4 +- theme/basic/img/btn_end.gif | Bin 0 -> 1176 bytes theme/basic/img/btn_first.gif | Bin 0 -> 1885 bytes theme/basic/img/btn_next.gif | Bin 0 -> 1169 bytes theme/basic/img/btn_prev.gif | Bin 0 -> 1873 bytes theme/basic/img/btn_top.gif | Bin 0 -> 1178 bytes theme/basic/img/close_btn.gif | Bin 0 -> 1209 bytes theme/basic/img/close_btn.png | Bin 0 -> 1147 bytes theme/basic/img/gnb_bg.gif | Bin 0 -> 1169 bytes theme/basic/img/gnb_bg2.gif | Bin 0 -> 49 bytes theme/basic/img/mobile/btn_close.png | Bin 0 -> 3265 bytes theme/basic/img/mobile/gnb_bg.png | Bin 0 -> 1012 bytes theme/basic/img/mobile/gnb_bg2.png | Bin 0 -> 976 bytes theme/basic/img/mobile/gnb_sch.png | Bin 0 -> 1795 bytes theme/basic/img/mobile/icon_menu.png | Bin 0 -> 1090 bytes theme/basic/img/mobile/icon_my.png | Bin 0 -> 1634 bytes theme/basic/img/no_profile.gif | Bin 0 -> 1953 bytes theme/basic/img/require.png | Bin 0 -> 2922 bytes theme/basic/img/sch_btn.png | Bin 0 -> 1234 bytes theme/basic/index.php | 6 +- theme/basic/mobile/head.php | 141 +- .../mobile/skin/board/basic/img/btn_next2.gif | Bin 0 -> 55 bytes .../mobile/skin/board/basic/img/btn_prev2.gif | Bin 0 -> 56 bytes .../mobile/skin/board/basic/img/cmt_btn.png | Bin 0 -> 2862 bytes .../mobile/skin/board/basic/img/icon_bad.png | Bin 0 -> 1287 bytes .../skin/board/basic/img/icon_comment.png | Bin 0 -> 1604 bytes .../mobile/skin/board/basic/img/icon_file.gif | Bin 107 -> 197 bytes .../mobile/skin/board/basic/img/icon_good.png | Bin 0 -> 1288 bytes .../mobile/skin/board/basic/img/icon_hot.gif | Bin 97 -> 319 bytes .../mobile/skin/board/basic/img/icon_link.gif | Bin 104 -> 319 bytes .../mobile/skin/board/basic/img/icon_lock.png | Bin 0 -> 1225 bytes .../skin/board/basic/img/icon_reply.gif | Bin 77 -> 1187 bytes .../skin/board/basic/img/icon_secret.gif | Bin 97 -> 318 bytes .../mobile/skin/board/basic/img/icon_view.png | Bin 0 -> 1936 bytes .../mobile/skin/board/basic/img/require.png | Bin 0 -> 2922 bytes .../mobile/skin/board/basic/img/sch_btn.png | Bin 0 -> 1234 bytes .../mobile/skin/board/basic/list.skin.php | 154 +- theme/basic/mobile/skin/board/basic/style.css | 256 +- .../mobile/skin/board/basic/view.skin.php | 222 +- .../skin/board/basic/view_comment.skin.php | 166 +- .../mobile/skin/board/basic/write.skin.php | 166 +- .../mobile/skin/board/gallery/list.skin.php | 146 +- .../basic/mobile/skin/board/gallery/style.css | 220 +- .../mobile/skin/board/gallery/view.skin.php | 224 +- .../skin/board/gallery/view_comment.skin.php | 166 +- .../mobile/skin/board/gallery/write.skin.php | 167 +- .../connect/basic/current_connect.skin.php | 30 +- .../basic/mobile/skin/connect/basic/style.css | 18 +- .../basic/mobile/skin/content/basic/style.css | 5 +- .../basic/mobile/skin/faq/basic/list.skin.php | 22 +- theme/basic/mobile/skin/faq/basic/style.css | 37 +- .../skin/latest/basic/img/icon_comment.png | Bin 0 -> 1604 bytes .../skin/latest/basic/img/icon_file.gif | Bin 107 -> 197 bytes .../mobile/skin/latest/basic/img/icon_hot.gif | Bin 97 -> 319 bytes .../mobile/skin/latest/basic/img/icon_img.gif | Bin 145 -> 317 bytes .../skin/latest/basic/img/icon_link.gif | Bin 104 -> 319 bytes .../skin/latest/basic/img/icon_mobile.gif | Bin 62 -> 339 bytes .../skin/latest/basic/img/icon_reply.gif | Bin 77 -> 1187 bytes .../skin/latest/basic/img/icon_secret.gif | Bin 97 -> 318 bytes .../mobile/skin/latest/basic/latest.skin.php | 17 +- .../basic/mobile/skin/latest/basic/style.css | 22 +- .../skin/member/basic/formmail.skin.php | 97 +- .../mobile/skin/member/basic/img/btn_del.gif | Bin 0 -> 1187 bytes .../skin/member/basic/img/icon_file.gif | Bin 0 -> 197 bytes .../mobile/skin/member/basic/login.skin.php | 21 +- .../skin/member/basic/member_confirm.skin.php | 4 - .../mobile/skin/member/basic/memo.skin.php | 48 +- .../skin/member/basic/memo_form.skin.php | 52 +- .../skin/member/basic/memo_view.skin.php | 63 +- .../skin/member/basic/password.skin.php | 3 - .../skin/member/basic/password_lost.skin.php | 31 +- .../mobile/skin/member/basic/point.skin.php | 125 +- .../mobile/skin/member/basic/profile.skin.php | 6 +- .../skin/member/basic/register.skin.php | 25 +- .../skin/member/basic/register_form.skin.php | 351 ++- .../member/basic/register_result.skin.php | 59 +- .../mobile/skin/member/basic/scrap.skin.php | 8 +- .../skin/member/basic/scrap_popin.skin.php | 43 +- .../basic/mobile/skin/member/basic/style.css | 183 +- .../basic/mobile/skin/new/basic/new.skin.php | 32 +- theme/basic/mobile/skin/new/basic/style.css | 14 +- .../skin/outlogin/basic/img/icon_edit.png | Bin 0 -> 1605 bytes .../skin/outlogin/basic/img/icon_memo.png | Bin 0 -> 1852 bytes .../skin/outlogin/basic/img/icon_point.png | Bin 0 -> 1923 bytes .../skin/outlogin/basic/img/icon_scrap.png | Bin 0 -> 1717 bytes .../skin/outlogin/basic/outlogin.skin.1.php | 9 +- .../skin/outlogin/basic/outlogin.skin.2.php | 27 +- .../mobile/skin/outlogin/basic/style.css | 44 +- .../mobile/skin/poll/basic/poll.skin.php | 6 +- .../skin/poll/basic/poll_result.skin.php | 161 +- theme/basic/mobile/skin/poll/basic/style.css | 97 +- .../skin/popular/basic/popular.skin.php | 10 +- .../basic/mobile/skin/popular/basic/style.css | 15 +- .../basic/mobile/skin/qa/basic/list.skin.php | 41 +- theme/basic/mobile/skin/qa/basic/style.css | 95 +- .../mobile/skin/qa/basic/view.answer.skin.php | 13 +- .../skin/qa/basic/view.answerform.skin.php | 36 +- .../basic/mobile/skin/qa/basic/view.skin.php | 227 +- .../basic/mobile/skin/qa/basic/write.skin.php | 124 +- .../mobile/skin/search/basic/search.skin.php | 35 +- .../basic/mobile/skin/search/basic/style.css | 52 +- theme/basic/mobile/skin/visit/basic/style.css | 20 +- .../mobile/skin/visit/basic/visit.skin.php | 26 +- theme/basic/mobile/tail.php | 45 +- .../basic/skin/board/basic/img/btn_next2.gif | Bin 0 -> 1898 bytes .../basic/skin/board/basic/img/btn_prev2.gif | Bin 0 -> 1185 bytes .../basic/skin/board/basic/img/close_btn.png | Bin 0 -> 1147 bytes theme/basic/skin/board/basic/img/cmt_btn.png | Bin 0 -> 2862 bytes theme/basic/skin/board/basic/img/icon_bad.png | Bin 0 -> 1287 bytes .../skin/board/basic/img/icon_comment.png | Bin 0 -> 3050 bytes .../basic/skin/board/basic/img/icon_file.gif | Bin 107 -> 197 bytes .../basic/skin/board/basic/img/icon_good.png | Bin 0 -> 1288 bytes theme/basic/skin/board/basic/img/icon_hot.gif | Bin 97 -> 319 bytes theme/basic/skin/board/basic/img/icon_img.gif | Bin 145 -> 317 bytes .../basic/skin/board/basic/img/icon_link.gif | Bin 104 -> 319 bytes .../basic/skin/board/basic/img/icon_lock.png | Bin 0 -> 1225 bytes .../skin/board/basic/img/icon_mobile.gif | Bin 62 -> 339 bytes theme/basic/skin/board/basic/img/icon_new.gif | Bin 71 -> 77 bytes .../basic/skin/board/basic/img/icon_reply.gif | Bin 77 -> 1187 bytes .../skin/board/basic/img/icon_secret.gif | Bin 97 -> 318 bytes .../basic/skin/board/basic/img/icon_share.png | Bin 0 -> 3064 bytes .../basic/skin/board/basic/img/icon_view.png | Bin 0 -> 3130 bytes theme/basic/skin/board/basic/img/sch_bg.png | Bin 0 -> 996 bytes theme/basic/skin/board/basic/img/sch_btn.png | Bin 0 -> 1234 bytes theme/basic/skin/board/basic/list.skin.php | 147 +- theme/basic/skin/board/basic/style.css | 246 +- theme/basic/skin/board/basic/view.skin.php | 275 +- .../skin/board/basic/view_comment.skin.php | 177 +- theme/basic/skin/board/basic/write.skin.php | 226 +- .../skin/board/gallery/img/btn_next2.gif | Bin 0 -> 1898 bytes .../skin/board/gallery/img/btn_prev2.gif | Bin 0 -> 1185 bytes .../skin/board/gallery/img/close_btn.png | Bin 0 -> 1147 bytes .../basic/skin/board/gallery/img/cmt_btn.png | Bin 0 -> 2862 bytes .../basic/skin/board/gallery/img/facebook.png | Bin 0 -> 2955 bytes theme/basic/skin/board/gallery/img/gplus.png | Bin 0 -> 3041 bytes .../basic/skin/board/gallery/img/icon_bad.png | Bin 0 -> 1287 bytes .../skin/board/gallery/img/icon_comment.png | Bin 0 -> 3050 bytes .../skin/board/gallery/img/icon_file.gif | Bin 107 -> 197 bytes .../skin/board/gallery/img/icon_good.png | Bin 0 -> 1288 bytes .../basic/skin/board/gallery/img/icon_hot.gif | Bin 97 -> 319 bytes .../skin/board/gallery/img/icon_link.gif | Bin 104 -> 319 bytes .../skin/board/gallery/img/icon_lock.png | Bin 0 -> 1225 bytes .../basic/skin/board/gallery/img/icon_new.gif | Bin 71 -> 77 bytes .../skin/board/gallery/img/icon_reply.gif | Bin 77 -> 1187 bytes .../skin/board/gallery/img/icon_secret.gif | Bin 97 -> 318 bytes .../skin/board/gallery/img/icon_share.png | Bin 0 -> 3064 bytes .../skin/board/gallery/img/icon_view.png | Bin 0 -> 3130 bytes theme/basic/skin/board/gallery/img/sch_bg.png | Bin 0 -> 996 bytes .../basic/skin/board/gallery/img/sch_btn.png | Bin 0 -> 1234 bytes .../basic/skin/board/gallery/img/twitter.png | Bin 0 -> 2995 bytes theme/basic/skin/board/gallery/list.skin.php | 136 +- theme/basic/skin/board/gallery/style.css | 243 +- theme/basic/skin/board/gallery/view.skin.php | 275 +- .../skin/board/gallery/view_comment.skin.php | 175 +- theme/basic/skin/board/gallery/write.skin.php | 226 +- .../connect/basic/current_connect.skin.php | 27 +- theme/basic/skin/connect/basic/style.css | 10 +- theme/basic/skin/faq/basic/list.skin.php | 32 +- theme/basic/skin/faq/basic/style.css | 32 +- .../basic/skin/latest/basic/img/icon_file.gif | Bin 107 -> 197 bytes .../basic/skin/latest/basic/img/icon_hot.gif | Bin 97 -> 319 bytes .../basic/skin/latest/basic/img/icon_img.gif | Bin 145 -> 317 bytes .../basic/skin/latest/basic/img/icon_link.gif | Bin 104 -> 319 bytes .../basic/skin/latest/basic/img/icon_lock.png | Bin 0 -> 1225 bytes .../skin/latest/basic/img/icon_mobile.gif | Bin 62 -> 339 bytes .../skin/latest/basic/img/icon_secret.gif | Bin 97 -> 318 bytes .../basic/skin/latest/basic/img/icon_view.png | Bin 0 -> 3130 bytes theme/basic/skin/latest/basic/img/more_bg.gif | Bin 0 -> 1172 bytes theme/basic/skin/latest/basic/latest.skin.php | 39 +- theme/basic/skin/latest/basic/style.css | 17 +- .../basic/skin/member/basic/formmail.skin.php | 97 +- theme/basic/skin/member/basic/login.skin.php | 26 +- .../skin/member/basic/member_confirm.skin.php | 11 +- theme/basic/skin/member/basic/memo.skin.php | 69 +- .../skin/member/basic/memo_form.skin.php | 80 +- .../skin/member/basic/memo_view.skin.php | 79 +- .../basic/skin/member/basic/password.skin.php | 10 +- .../skin/member/basic/password_lost.skin.php | 29 +- theme/basic/skin/member/basic/point.skin.php | 131 +- .../basic/skin/member/basic/profile.skin.php | 48 +- .../basic/skin/member/basic/register.skin.php | 12 +- .../skin/member/basic/register_form.skin.php | 354 ++- .../member/basic/register_result.skin.php | 10 +- theme/basic/skin/member/basic/scrap.skin.php | 43 +- .../skin/member/basic/scrap_popin.skin.php | 43 +- theme/basic/skin/member/basic/style.css | 179 +- theme/basic/skin/new/basic/new.skin.php | 22 +- theme/basic/skin/new/basic/style.css | 11 +- .../skin/outlogin/basic/img/info_edit.png | Bin 0 -> 1341 bytes .../skin/outlogin/basic/outlogin.skin.1.php | 34 +- .../skin/outlogin/basic/outlogin.skin.2.php | 18 +- theme/basic/skin/outlogin/basic/style.css | 53 +- theme/basic/skin/poll/basic/poll.skin.php | 26 +- .../skin/poll/basic/poll_result.skin.php | 181 +- theme/basic/skin/poll/basic/style.css | 83 +- .../basic/skin/popular/basic/popular.skin.php | 6 +- theme/basic/skin/popular/basic/style.css | 14 +- theme/basic/skin/qa/basic/list.skin.php | 68 +- theme/basic/skin/qa/basic/style.css | 133 +- .../basic/skin/qa/basic/view.answer.skin.php | 10 +- .../skin/qa/basic/view.answerform.skin.php | 42 +- theme/basic/skin/qa/basic/view.skin.php | 150 +- theme/basic/skin/qa/basic/write.skin.php | 137 +- theme/basic/skin/search/basic/search.skin.php | 34 +- theme/basic/skin/search/basic/style.css | 51 +- theme/basic/skin/visit/basic/style.css | 19 +- theme/basic/skin/visit/basic/visit.skin.php | 26 +- theme/basic/tail.php | 20 +- theme/basic/theme.config.php | 4 +- 359 files changed, 24621 insertions(+), 4963 deletions(-) create mode 100644 adm/img/social/sns_logo.png create mode 100644 adm/img/social/sns_logo_not.png create mode 100644 extend/social_login.extend.php create mode 100644 img/ft_logo.jpg create mode 100644 img/m_logo.png create mode 100644 img/ts01.png create mode 100644 img/ts02.png create mode 100644 img/ts03.png create mode 100644 js/font-awesome/HELP-US-OUT.txt create mode 100644 js/font-awesome/css/font-awesome.css create mode 100644 js/font-awesome/css/font-awesome.min.css create mode 100644 js/font-awesome/fonts/FontAwesome.otf create mode 100644 js/font-awesome/fonts/fontawesome-webfont.eot create mode 100644 js/font-awesome/fonts/fontawesome-webfont.svg create mode 100644 js/font-awesome/fonts/fontawesome-webfont.ttf create mode 100644 js/font-awesome/fonts/fontawesome-webfont.woff create mode 100644 js/font-awesome/fonts/fontawesome-webfont.woff2 create mode 100644 js/font-awesome/less/animated.less create mode 100644 js/font-awesome/less/bordered-pulled.less create mode 100644 js/font-awesome/less/core.less create mode 100644 js/font-awesome/less/fixed-width.less create mode 100644 js/font-awesome/less/font-awesome.less create mode 100644 js/font-awesome/less/icons.less create mode 100644 js/font-awesome/less/larger.less create mode 100644 js/font-awesome/less/list.less create mode 100644 js/font-awesome/less/mixins.less create mode 100644 js/font-awesome/less/path.less create mode 100644 js/font-awesome/less/rotated-flipped.less create mode 100644 js/font-awesome/less/screen-reader.less create mode 100644 js/font-awesome/less/stacked.less create mode 100644 js/font-awesome/less/variables.less create mode 100644 js/font-awesome/scss/_animated.scss create mode 100644 js/font-awesome/scss/_bordered-pulled.scss create mode 100644 js/font-awesome/scss/_core.scss create mode 100644 js/font-awesome/scss/_fixed-width.scss create mode 100644 js/font-awesome/scss/_icons.scss create mode 100644 js/font-awesome/scss/_larger.scss create mode 100644 js/font-awesome/scss/_list.scss create mode 100644 js/font-awesome/scss/_mixins.scss create mode 100644 js/font-awesome/scss/_path.scss create mode 100644 js/font-awesome/scss/_rotated-flipped.scss create mode 100644 js/font-awesome/scss/_screen-reader.scss create mode 100644 js/font-awesome/scss/_stacked.scss create mode 100644 js/font-awesome/scss/_variables.scss create mode 100644 js/font-awesome/scss/font-awesome.scss create mode 100644 js/placeholders.min.js create mode 100644 lib/social_member.lib.php create mode 100644 mobile/skin/social/img/sns_logo.png create mode 100644 mobile/skin/social/img/sns_logo_not.png create mode 100644 mobile/skin/social/index.php create mode 100644 mobile/skin/social/register_social_member.skin.php create mode 100644 mobile/skin/social/social_login_icon.skin.php create mode 100644 mobile/skin/social/social_member_form.skin.php create mode 100644 mobile/skin/social/style.css create mode 100644 plugin/recaptcha/_common.php create mode 100644 plugin/recaptcha/captcha.lib.php create mode 100644 plugin/recaptcha/recaptcha.class.php create mode 100644 plugin/recaptcha/recaptcha.js create mode 100644 plugin/recaptcha/recaptcha.user.lib.php create mode 100644 plugin/social/Hybrid/Auth.php create mode 100644 plugin/social/Hybrid/Endpoint.php create mode 100644 plugin/social/Hybrid/Error.php create mode 100644 plugin/social/Hybrid/Exception.php create mode 100644 plugin/social/Hybrid/Logger.php create mode 100644 plugin/social/Hybrid/Provider_Adapter.php create mode 100644 plugin/social/Hybrid/Provider_Model.php create mode 100644 plugin/social/Hybrid/Provider_Model_OAuth1.php create mode 100644 plugin/social/Hybrid/Provider_Model_OAuth2.php create mode 100644 plugin/social/Hybrid/Provider_Model_OpenID.php create mode 100644 plugin/social/Hybrid/Providers/Facebook.php create mode 100644 plugin/social/Hybrid/Providers/Google.php create mode 100644 plugin/social/Hybrid/Providers/Kakao.php create mode 100644 plugin/social/Hybrid/Providers/Naver.php create mode 100644 plugin/social/Hybrid/Providers/Payco.php create mode 100644 plugin/social/Hybrid/Providers/Twitter.php create mode 100644 plugin/social/Hybrid/Storage.php create mode 100644 plugin/social/Hybrid/StorageInterface.php create mode 100644 plugin/social/Hybrid/User.php create mode 100644 plugin/social/Hybrid/User_Activity.php create mode 100644 plugin/social/Hybrid/User_Contact.php create mode 100644 plugin/social/Hybrid/User_Profile.php create mode 100644 plugin/social/Hybrid/index.html create mode 100644 plugin/social/Hybrid/resources/index.html create mode 100644 plugin/social/Hybrid/resources/openid_policy.html create mode 100644 plugin/social/Hybrid/resources/openid_realm.html create mode 100644 plugin/social/Hybrid/resources/openid_xrds.xml create mode 100644 plugin/social/Hybrid/thirdparty/OAuth/OAuth.php create mode 100644 plugin/social/Hybrid/thirdparty/OAuth/OAuth1Client.php create mode 100644 plugin/social/Hybrid/thirdparty/OAuth/OAuth2Client.php create mode 100644 plugin/social/Hybrid/thirdparty/OpenID/LightOpenID.php create mode 100644 plugin/social/Hybrid/thirdparty/index.html create mode 100644 plugin/social/_common.php create mode 100644 plugin/social/config.php create mode 100644 plugin/social/error.php create mode 100644 plugin/social/img/loading_icon.gif create mode 100644 plugin/social/includes/functions.php create mode 100644 plugin/social/includes/g5_endpoint.php create mode 100644 plugin/social/includes/index.php create mode 100644 plugin/social/includes/loading.php create mode 100644 plugin/social/includes/providers.php create mode 100644 plugin/social/index.php create mode 100644 plugin/social/popup.php create mode 100644 plugin/social/register_member.php create mode 100644 plugin/social/register_member_update.php create mode 100644 plugin/social/unlink.php create mode 100644 skin/social/img/sns_logo.png create mode 100644 skin/social/img/sns_logo_not.png create mode 100644 skin/social/index.php create mode 100644 skin/social/register_social_member.skin.php create mode 100644 skin/social/social_login_icon.skin.php create mode 100644 skin/social/social_member_form.skin.php create mode 100644 skin/social/style.css create mode 100644 theme/basic/img/btn_end.gif create mode 100644 theme/basic/img/btn_first.gif create mode 100644 theme/basic/img/btn_next.gif create mode 100644 theme/basic/img/btn_prev.gif create mode 100644 theme/basic/img/btn_top.gif create mode 100644 theme/basic/img/close_btn.gif create mode 100644 theme/basic/img/close_btn.png create mode 100644 theme/basic/img/gnb_bg.gif create mode 100644 theme/basic/img/gnb_bg2.gif create mode 100644 theme/basic/img/mobile/btn_close.png create mode 100644 theme/basic/img/mobile/gnb_bg.png create mode 100644 theme/basic/img/mobile/gnb_bg2.png create mode 100644 theme/basic/img/mobile/gnb_sch.png create mode 100644 theme/basic/img/mobile/icon_menu.png create mode 100644 theme/basic/img/mobile/icon_my.png create mode 100644 theme/basic/img/no_profile.gif create mode 100644 theme/basic/img/require.png create mode 100644 theme/basic/img/sch_btn.png create mode 100644 theme/basic/mobile/skin/board/basic/img/btn_next2.gif create mode 100644 theme/basic/mobile/skin/board/basic/img/btn_prev2.gif create mode 100644 theme/basic/mobile/skin/board/basic/img/cmt_btn.png create mode 100644 theme/basic/mobile/skin/board/basic/img/icon_bad.png create mode 100644 theme/basic/mobile/skin/board/basic/img/icon_comment.png create mode 100644 theme/basic/mobile/skin/board/basic/img/icon_good.png create mode 100644 theme/basic/mobile/skin/board/basic/img/icon_lock.png create mode 100644 theme/basic/mobile/skin/board/basic/img/icon_view.png create mode 100644 theme/basic/mobile/skin/board/basic/img/require.png create mode 100644 theme/basic/mobile/skin/board/basic/img/sch_btn.png create mode 100644 theme/basic/mobile/skin/latest/basic/img/icon_comment.png create mode 100644 theme/basic/mobile/skin/member/basic/img/btn_del.gif create mode 100644 theme/basic/mobile/skin/member/basic/img/icon_file.gif create mode 100644 theme/basic/mobile/skin/outlogin/basic/img/icon_edit.png create mode 100644 theme/basic/mobile/skin/outlogin/basic/img/icon_memo.png create mode 100644 theme/basic/mobile/skin/outlogin/basic/img/icon_point.png create mode 100644 theme/basic/mobile/skin/outlogin/basic/img/icon_scrap.png create mode 100644 theme/basic/skin/board/basic/img/btn_next2.gif create mode 100644 theme/basic/skin/board/basic/img/btn_prev2.gif create mode 100644 theme/basic/skin/board/basic/img/close_btn.png create mode 100644 theme/basic/skin/board/basic/img/cmt_btn.png create mode 100644 theme/basic/skin/board/basic/img/icon_bad.png create mode 100644 theme/basic/skin/board/basic/img/icon_comment.png create mode 100644 theme/basic/skin/board/basic/img/icon_good.png create mode 100644 theme/basic/skin/board/basic/img/icon_lock.png create mode 100644 theme/basic/skin/board/basic/img/icon_share.png create mode 100644 theme/basic/skin/board/basic/img/icon_view.png create mode 100644 theme/basic/skin/board/basic/img/sch_bg.png create mode 100644 theme/basic/skin/board/basic/img/sch_btn.png create mode 100644 theme/basic/skin/board/gallery/img/btn_next2.gif create mode 100644 theme/basic/skin/board/gallery/img/btn_prev2.gif create mode 100644 theme/basic/skin/board/gallery/img/close_btn.png create mode 100644 theme/basic/skin/board/gallery/img/cmt_btn.png create mode 100644 theme/basic/skin/board/gallery/img/facebook.png create mode 100644 theme/basic/skin/board/gallery/img/gplus.png create mode 100644 theme/basic/skin/board/gallery/img/icon_bad.png create mode 100644 theme/basic/skin/board/gallery/img/icon_comment.png create mode 100644 theme/basic/skin/board/gallery/img/icon_good.png create mode 100644 theme/basic/skin/board/gallery/img/icon_lock.png create mode 100644 theme/basic/skin/board/gallery/img/icon_share.png create mode 100644 theme/basic/skin/board/gallery/img/icon_view.png create mode 100644 theme/basic/skin/board/gallery/img/sch_bg.png create mode 100644 theme/basic/skin/board/gallery/img/sch_btn.png create mode 100644 theme/basic/skin/board/gallery/img/twitter.png create mode 100644 theme/basic/skin/latest/basic/img/icon_lock.png create mode 100644 theme/basic/skin/latest/basic/img/icon_view.png create mode 100644 theme/basic/skin/latest/basic/img/more_bg.gif create mode 100644 theme/basic/skin/outlogin/basic/img/info_edit.png diff --git a/adm/config_form.php b/adm/config_form.php index 7c2c57ec8..46aaaa244 100644 --- a/adm/config_form.php +++ b/adm/config_form.php @@ -192,6 +192,49 @@ if(!sql_query(" select vi_browser from {$g5['visit_table']} limit 1 ")) { ADD `vi_device` varchar(255) NOT NULL DEFAULT '' AFTER `vi_os` ", true); } +//소셜 로그인 관련 필드 및 구글 리챕챠 필드 추가 +if(!isset($config['cf_social_login_use'])) { + sql_query("ALTER TABLE `{$g5['config_table']}` + ADD `cf_social_login_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_googl_shorturl_apikey`, + ADD `cf_google_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_twitter_secret`, + ADD `cf_google_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_clientid`, + ADD `cf_naver_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_secret`, + ADD `cf_naver_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_clientid`, + ADD `cf_kakao_rest_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_secret`, + ADD `cf_social_servicelist` varchar(255) NOT NULL DEFAULT '' AFTER `cf_social_login_use`, + ADD `cf_payco_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_social_servicelist`, + ADD `cf_payco_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_payco_clientid`, + ADD `cf_captcha` varchar(100) NOT NULL DEFAULT '' AFTER `cf_kakao_js_apikey`, + ADD `cf_recaptcha_site_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_captcha`, + ADD `cf_recaptcha_secret_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_recaptcha_site_key` + ", true); +} + +// 소셜 로그인 관리 테이블 없을 경우 생성 +if(!sql_query(" DESC {$g5['social_profile_table']} ", false)) { + sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['social_profile_table']}` ( + `mp_no` int(11) NOT NULL AUTO_INCREMENT, + `mb_id` varchar(255) NOT NULL DEFAULT '', + `provider` varchar(50) NOT NULL DEFAULT '', + `object_sha` varchar(45) NOT NULL DEFAULT '', + `identifier` varchar(255) NOT NULL DEFAULT '', + `profileurl` varchar(255) NOT NULL DEFAULT '', + `photourl` varchar(255) NOT NULL DEFAULT '', + `displayname` varchar(150) NOT NULL DEFAULT '', + `description` varchar(255) NOT NULL DEFAULT '', + `gender` varchar(10) NOT NULL DEFAULT '', + `age` varchar(10) NOT NULL DEFAULT '', + `birthday` int(11) NOT NULL DEFAULT '0', + `birthmonth` int(11) NOT NULL DEFAULT '0', + `birthyear` int(11) NOT NULL DEFAULT '0', + `mp_latest_day` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + UNIQUE KEY `mp_no` (`mp_no`), + KEY `mb_id` (`mb_id`), + KEY `provider` (`provider`) + ) ", true); +} + + if(!$config['cf_faq_skin']) $config['cf_faq_skin'] = "basic"; if(!$config['cf_mobile_faq_skin']) $config['cf_mobile_faq_skin'] = "basic"; @@ -403,12 +446,22 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) { + + + + + + + - + + + + + reCAPTCHA 등록하기 + + + + + + @@ -959,10 +1022,75 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) { + + + + + > 사용 + + + + + +

+ > + +

+

네이버 CallbackURL

+

/?hauth.done=Naver

+
+

+

+ > + +

+

카카오 웹 Redirect Path

+

+ + +

+
+

+

+ > + +

+

페이스북 CallbackURL

+

/?hauth.done=Facebook

+
+

+

+ > + +

+

구글 CallbackURL

+

/?hauth.done=Google

+
+

+

+ > + +

+

+ > + +

+ + + + + + 앱 등록하기 + + + + + + - 앱 등록하기 + 앱 등록하기 @@ -972,7 +1100,7 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) { - 앱 등록하기 + 앱 등록하기 @@ -980,13 +1108,39 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) { - + - API Key 등록하기 + 앱 등록하기 - + - 앱 등록하기 + + + + + + + API Key 등록하기 + + + + + + 앱 등록하기 + + + + + + + + + + 앱 등록하기 + + + + @@ -1161,6 +1315,13 @@ $(function(){ break; } }); + $("#cf_captcha").on("change", function(){ + if ($(this).val() == 'recaptcha') { + $("[class^='kcaptcha_']").hide(); + } else { + $("[class^='kcaptcha_']").show(); + } + }).trigger("change"); $(".get_theme_confc").on("click", function() { var type = $(this).data("type"); diff --git a/adm/config_form_update.php b/adm/config_form_update.php index 773e5408a..1896bcea2 100644 --- a/adm/config_form_update.php +++ b/adm/config_form_update.php @@ -24,6 +24,8 @@ if(!$_POST['cf_cert_use']) { $_POST['cf_cert_hp'] = ''; } +$cf_social_servicelist = !empty($_POST['cf_social_servicelist']) ? implode(',', $_POST['cf_social_servicelist']) : ''; + $sql = " update {$g5['config_table']} set cf_title = '{$_POST['cf_title']}', cf_admin = '{$_POST['cf_admin']}', @@ -135,6 +137,18 @@ $sql = " update {$g5['config_table']} cf_facebook_secret = '{$_POST['cf_facebook_secret']}', cf_twitter_key = '{$_POST['cf_twitter_key']}', cf_twitter_secret = '{$_POST['cf_twitter_secret']}', + cf_social_login_use = '{$_POST['cf_social_login_use']}', + cf_naver_clientid = '{$_POST['cf_naver_clientid']}', + cf_naver_secret = '{$_POST['cf_naver_secret']}', + cf_google_clientid = '{$_POST['cf_google_clientid']}', + cf_google_secret = '{$_POST['cf_google_secret']}', + cf_kakao_rest_key = '{$_POST['cf_kakao_rest_key']}', + cf_social_servicelist = '{$cf_social_servicelist}', + cf_captcha = '{$_POST['cf_captcha']}', + cf_recaptcha_site_key = '{$_POST['cf_recaptcha_site_key']}', + cf_recaptcha_secret_key = '{$_POST['cf_recaptcha_secret_key']}', + cf_payco_clientid = '{$_POST['cf_payco_clientid']}', + cf_payco_secret = '{$_POST['cf_payco_secret']}', cf_1_subj = '{$_POST['cf_1_subj']}', cf_2_subj = '{$_POST['cf_2_subj']}', cf_3_subj = '{$_POST['cf_3_subj']}', diff --git a/adm/css/admin.css b/adm/css/admin.css index be142eda0..ae9cc8b73 100644 --- a/adm/css/admin.css +++ b/adm/css/admin.css @@ -692,6 +692,37 @@ strong.sodr_nonpay {display:block;padding:5px 0;text-align:right} .pg_end, .pg_next {/* 다음 */} .pg_current {display:inline-block;margin:0 4px 0 0;background:#333;color:#fff;font-weight:normal} +/* social member_list.php member_form.php */ +.sns-icon .ico {display:inline-block;background:url('../img/social/sns_logo.png') no-repeat;vertical-align:middle} +.sns-icon .txt {position:absolute;line-height:0;font-size:0;vertical-align:middle;overflow:hidden} + +.sns-wrap-over .sns-icon {border-color:#dcdcdc} +.sns-wrap-over .sns-icon-not {border-color:#8b8b8b} +.sns-wrap-over .sns-naver{border-color:#18a400} +.sns-wrap-over .sns-naver .ico {background-position:-29px 0; } +.sns-wrap-over .sns-google .ico {background-position:-58px 0} +.sns-wrap-over .sns-google {border-color:#ca2c19} +.sns-wrap-over .sns-facebook .ico {background-position:0 0 } +.sns-wrap-over .sns-facebook {border-color:#2e5393} +.sns-wrap-over .sns-kakao .ico {background-position:-87px 0} +.sns-wrap-over .sns-twitter {border-color:#488FC9} +.sns-wrap-over .sns-twitter .ico {background-position:-145px 0} +.sns-wrap-over .sns-payco {border-color:#C44646} +.sns-wrap-over .sns-payco .ico {background-position:-116px 0} +.sns-wrap-over .sns-kakao {border-color:#f2df00} + +.sns-wrap-32 .sns-icon {display:inline-block;border-width:1px;border-style:solid;overflow:hidden;margin:0 1px} +.sns-wrap-32 .sns-icon .txt {margin:0 10px 0 0;font-size:0.95em;letter-spacing:-0.1em} +.sns-wrap-32 .sns-icon .ico {width:24px;height:24px} + +.social_link_box, .social_link_box li{margin:0;padding:0;list-style:none} +.social_link_box{width:300px} +.account_provider{position:relative;margin:10px 0 5px} +.account_provider .sns-wrap-32 .sns-icon{vertical-align:middle;} +.account_provider .provider_name{display:inline-block;vertical-align:middle;} +.account_provider .social_unlink{position:absolute;top:-7px;right:0; height:30px;line-height:30px;background:#a0a8b8;padding:0 10px;color:#fff;display: inline-block;vertical-align: middle;} +.account_provider .social_unlink:hover{text-decoration:none;} + /*부가서비스*/ .lnb_svc{color:#ff3061;font-weight:bold} .service_wrap{width:960px; margin:0 20px 10px;overflow:hidden} diff --git a/adm/img/social/sns_logo.png b/adm/img/social/sns_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..958bb5788a999033931860f3ae9bab13931d9104 GIT binary patch literal 6045 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 XSy;i`Jew{v00000NkvXXu0mjf(et`f literal 0 HcmV?d00001 diff --git a/adm/img/social/sns_logo_not.png b/adm/img/social/sns_logo_not.png new file mode 100644 index 0000000000000000000000000000000000000000..04308576c99623fd23f63b21a2a1f42cd913978d GIT binary patch literal 5465 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 P00000NkvXXu0mjfe$r_B literal 0 HcmV?d00001 diff --git a/adm/member_form.php b/adm/member_form.php index 14825a339..258611837 100644 --- a/adm/member_form.php +++ b/adm/member_form.php @@ -339,6 +339,94 @@ this.form.mb_intercept_date.value=this.form.mb_intercept_date.defaultValue; }"> + + + + 소셜계정목록 + + + + + + + + + diff --git a/adm/member_list.php b/adm/member_list.php index 45dd3d395..c3c7cda96 100644 --- a/adm/member_list.php +++ b/adm/member_list.php @@ -215,7 +215,32 @@ $colspan = 16; - + + + '; + foreach( (array) $my_social_accounts as $account){ //반복문 + + if( empty($account) || empty($account['provider']) ) continue; + + $provider = strtolower($account['provider']); + $provider_name = social_get_provider_service_name($provider); + + echo ''; + echo ''; + echo ''.$provider_name.''; + echo ''; + + } + echo ''; + } + } + ?> + > diff --git a/bbs/board.php b/bbs/board.php index 4d734128c..aeecf487f 100644 --- a/bbs/board.php +++ b/bbs/board.php @@ -136,7 +136,7 @@ if (isset($wr_id) && $wr_id) { set_session($ss_name, TRUE); } - $g5['title'] = strip_tags(conv_subject($write['wr_subject'], 255))." > ".((G5_IS_MOBILE && $board['bo_mobile_subject']) ? $board['bo_mobile_subject'] : $board['bo_subject']); + $g5['title'] = ((G5_IS_MOBILE && $board['bo_mobile_subject']) ? $board['bo_mobile_subject'] : $board['bo_subject']); } else { if ($member['mb_level'] < $board['bo_list_level']) { if ($member['mb_id']) @@ -171,7 +171,7 @@ if (isset($wr_id) && $wr_id) { if (!isset($page) || (isset($page) && $page == 0)) $page = 1; - $g5['title'] = ((G5_IS_MOBILE && $board['bo_mobile_subject']) ? $board['bo_mobile_subject'] : $board['bo_subject']).' '.$page.' 페이지'; + $g5['title'] = ((G5_IS_MOBILE && $board['bo_mobile_subject']) ? $board['bo_mobile_subject'] : $board['bo_subject']) ; } include_once(G5_PATH.'/head.sub.php'); diff --git a/bbs/login.php b/bbs/login.php index a9e343239..30a3fe867 100644 --- a/bbs/login.php +++ b/bbs/login.php @@ -1,6 +1,10 @@ $value) { - if ($key != 'mb_id' && $key != 'mb_password' && $key != 'x' && $key != 'y' && $key != 'url') { + if ($key && !in_array($key, $post_check_keys)) { $link .= "$split$key=$value"; $split = "&"; } } + } else { $link = G5_URL; } +//소셜 로그인 추가 +if(function_exists('social_login_success_after')){ + // 로그인 성공시 소셜 데이터를 기존의 데이터와 비교하여 바뀐 부분이 있으면 업데이트 합니다. + $link = social_login_success_after($mb, $link); +} + goto_url($link); ?> diff --git a/bbs/logout.php b/bbs/logout.php index 3bcb99cef..7783d6e99 100644 --- a/bbs/logout.php +++ b/bbs/logout.php @@ -1,6 +1,10 @@ diff --git a/bbs/move.php b/bbs/move.php index a9441e4b4..e7f214f3a 100644 --- a/bbs/move.php +++ b/bbs/move.php @@ -57,50 +57,43 @@ for ($i=0; $row=sql_fetch_array($result); $i++) -
- - - - - - - - - - 현재게시판'; - $atc_bg = 'copymove_currentbg'; - } - ?> - - - - - - -
할 게시판을 한개 이상 선택하여 주십시오.
- - - 게시판
- - - - -
-
+
+ 할 게시판을 한개 이상 선택하여 주십시오. +
+ + +
+
    + 현재게시판'; + $atc_bg = 'copymove_currentbg'; + } + ?> +
  • + + + + + + + +
  • + +
-
- +
+ +
@@ -108,7 +101,7 @@ for ($i=0; $row=sql_fetch_array($result); $i++) + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + +
사이트 이용정보 입력
+ 영문자, 숫자, _ 만 입력 가능. 최소 3자이상 입력하세요. + class="frm_input " minlength="3" maxlength="20"> + +
class="frm_input " minlength="3" maxlength="20">
class="frm_input " minlength="3" maxlength="20">
+
+ +
+ + + + + + + + + + + + + + +
개인정보 입력
+ + 공백없이 한글,영문,숫자만 입력 가능 (한글2자, 영문4자 이상)
+ 닉네임을 바꾸시면 앞으로 일 이내에는 변경 할 수 없습니다. +
+ + + +
+ + + + + + + +
+
+ +
+ + 취소 +
+
+ + + + + + + + +
+ \ No newline at end of file diff --git a/mobile/skin/social/social_login_icon.skin.php b/mobile/skin/social/social_login_icon.skin.php new file mode 100644 index 000000000..e2effaf75 --- /dev/null +++ b/mobile/skin/social/social_login_icon.skin.php @@ -0,0 +1,83 @@ +', 10); +?> + + \ No newline at end of file diff --git a/mobile/skin/social/social_member_form.skin.php b/mobile/skin/social/social_member_form.skin.php new file mode 100644 index 000000000..8703b06e6 --- /dev/null +++ b/mobile/skin/social/social_member_form.skin.php @@ -0,0 +1,164 @@ +', 10); +?> + + + 소셜로그인 + +
+
+ + $provider_name ){ + + if( !option_array_checked($social, $config['cf_social_servicelist'])) { + continue; + } + + $social_nonce = social_nonce_create($social, $session_id); + $add_class=''; + $title=''; + if( in_array($social, $my_provides) ){ + + $link_href = G5_SOCIAL_LOGIN_URL.'/unlink.php?provider='.$social.'&social_nonce='.$social_nonce; + + $title = $provider_name.' 연결해제하기'; + } else { + $add_class = ' sns-icon-not'; + + $link_href = $self_url.'?provider='.$social.'&mylink=1&url='.$urlencode; + + $title = $provider_name.' 연결하기'; + + } + ?> + + + + + +
+
+ + + + \ No newline at end of file diff --git a/mobile/skin/social/style.css b/mobile/skin/social/style.css new file mode 100644 index 000000000..627e827fe --- /dev/null +++ b/mobile/skin/social/style.css @@ -0,0 +1,100 @@ +@charset "utf-8"; + +/* LOGIN WRAP */ +#mb_login {margin:0 auto;padding:100px 0;max-width:500px} +#mb_login h1 {margin:0 0 20px;font-size:1.3em} +#mb_login h2 {margin:0} +#mb_login p {padding:10px 0;line-height:1.5em} +#mb_login #login_fs {position:relative;margin:0;padding:20px 20px 20px 95px;border:1px solid #cfded8;border-bottom:0;background:#fff} +#mb_login #login_fs legend {position:absolute;margin:0;padding:0;font-size:0;line-height:0;text-indent:-9999em;overflow:hidden} +#mb_login #login_fs label {letter-spacing:-0.1em} +#mb_login #login_fs .login_id {position:absolute;top:26px;left:95px} +#mb_login #login_fs .login_pw {position:absolute;top:52px;left:95px} +#mb_login #login_fs .frm_input {display:block;margin:0 0 5px 80px;width:162px} +#mb_login #login_fs .btn_submit {position:absolute;top:20px;right:95px;width:60px;height:53px} +#mb_login #login_info {margin:0 0 30px;padding:20px;border:1px solid #cfded8;background:#f5f6fa} +#mb_login #login_info div {text-align:right} + +#mb_login_notmb {position:relative;margin:0 0 20px;padding:20px;border:1px solid #cfded8;background:#fff} +#mb_login_notmb #guest_privacy {margin:0 0 10px;padding:10px;height:150px;border:1px solid #e9e9e9;overflow:auto} +#mb_login_notmb .btn_confirm {padding:10px 0 0} + +#mb_login_od {position:relative;margin:0;padding:20px 20px 20px 95px;border:1px solid #cfded8;border-bottom:0;background:#fff} +#mb_login_od legend {position:absolute;font-size:0;line-height:0;overflow:hidden} +#mb_login_od label {letter-spacing:-0.1em} +#mb_login_od .od_id {position:absolute;top:26px;left:95px} +#mb_login_od .od_pwd {position:absolute;top:52px;left:95px} +#mb_login_od .frm_input {display:block;margin:0 0 5px 80px} +#mb_login_od .btn_submit {position:absolute;top:20px;left:335px;width:52px;height:49px} +#mb_login_odinfo {margin:0 0 30px;padding:20px;border:1px solid #cfded8;background:#f5f6fa} +#mb_login_odinfo div {text-align:right} + +.social_info_guide{background:#f3f3f3;border:1px solid #ddd;margin:0 10px 10px;padding:10px 15px;line-height:1.5em} +.bg-warning{margin:0 10px 10px;padding:10px 15px;line-height:1.5em} +.bg-warning1{background:#f7dfe4;border:1px solid #eac3cb} +.bg-warning2{background:#deeabf;border:1px solid #bde498} +.bg-warning3{background:#fff8dc;border:1px solid #f1e4b2} + +/* LOGIN WRAP 640px*/ +@media screen and (max-width: 40.000em){ + #mb_login {margin:0 auto;padding:20px 0;} + #mb_login h1 {margin:0 0 15px;padding:0 10px;} + + #mb_login #login_fs {padding:10px;} + #mb_login #login_fs .login_id, + #mb_login #login_fs .login_pw {display:block;position:inherit;top:0px;left:0} + #mb_login #login_fs .frm_input {margin:0 0 5px 0;width:60%} + #mb_login #login_fs .btn_submit {top:24px;right:10px;width:35%;height:67px} + + #mb_login_notmb {margin:30px 0;padding:15px 10px;border:1px solid #cfded8;background:#f7f7f7} + #mb_login_notmb .btn_confirm {margin:20px 0 0;text-align:right} + + #mb_login_od {margin:20px 5px;} + #mb_login_od .frm_input {margin-bottom:5px;padding:0;width:80%;height:1.8em;line-height:1.8em} + #mb_login_od .btn_submit {position:absolute;top:0;right:0;padding:0 !important;width:18%;height:4em !important;text-align:center} +} + +/* SNS LOGIN */ +.sns-wrap {margin:10px 0 0;text-align:center} +.sns-icon {display:inline-block;vertical-align:middle;text-decoration:none} +.sns-icon:hover {text-decoration:none} +.sns-icon .ico {display:block;background:url('./img/sns_logo.png') no-repeat;vertical-align:middle} +.sns-icon .txt {position:absolute;line-height:0;font-size:0;vertical-align:middle;overflow:hidden} +.sns-icon .txt i {font-style:normal} + +.reg-form .sns-wrap {margin:0;text-align:left} +.reg-form .sns-icon {display:inline-block;vertical-align:middle;text-decoration:none} +.reg-form .sns-icon:hover {text-decoration:none} +.reg-form .sns-icon .ico {display:block;background:url('./img/sns_logo.png') no-repeat;vertical-align:middle} +.reg-form .sns-icon-not .ico {display:block;background:url('./img/sns_logo_not.png') no-repeat;vertical-align:middle} +.reg-form .sns-icon .txt {position:absolute;line-height:0;font-size:0;vertical-align:middle;overflow:hidden} +.reg-form .sns-icon .txt i {font-style:normal} + +/* SNS by COLOR */ +.sns-wrap-org .sns-naver {border-color:#18a400;background:#2db400} +.sns-wrap-org .sns-naver .ico {background-position:-29px 0; } +.sns-wrap-org .sns-google {border-color:#ca2c19;background:#dd5443} +.sns-wrap-org .sns-google .ico {background-position:-58px 0} +.sns-wrap-org .sns-facebook {border-color:#2e5393;background:#3a5897} +.sns-wrap-org .sns-facebook .ico {background-position:0 0 } + +.sns-wrap-over .sns-icon {border-color:#dcdcdc} +.sns-wrap-over .sns-icon-not {border-color:#8b8b8b} +.sns-wrap-over .sns-naver{border-color:#18a400} +.sns-wrap-over .sns-naver .ico {background-position:-29px 0; } +.sns-wrap-over .sns-google .ico {background-position:-58px 0} +.sns-wrap-over .sns-google {border-color:#ca2c19} +.sns-wrap-over .sns-facebook .ico {background-position:0 0 } +.sns-wrap-over .sns-facebook {border-color:#2e5393} +.sns-wrap-over .sns-kakao .ico {background-position:-87px 0} +.sns-wrap-over .sns-twitter {border-color:#488FC9} +.sns-wrap-over .sns-twitter .ico {background-position:-145px 0} +.sns-wrap-over .sns-payco {border-color:#C44646} +.sns-wrap-over .sns-payco .ico {background-position:-116px 0} +.sns-wrap-over .sns-kakao {border-color:#f2df00} +.reg-form .sns-icon-not {border-color:#8b8b8b} + +/* SNS by SIZE */ +.sns-wrap-32 .sns-icon {border-width:1px;border-style:solid;overflow:hidden;margin:0 1px} +.sns-wrap-32 .sns-icon .txt {display:inline-block;margin:0 10px 0 0;font-size:0.95em;letter-spacing:-0.1em} +.sns-wrap-32 .sns-icon .ico {width:24px;height:24px} \ No newline at end of file diff --git a/plugin/editor/smarteditor2/css/smart_editor2.css b/plugin/editor/smarteditor2/css/smart_editor2.css index 6fde2758d..881272d8d 100644 --- a/plugin/editor/smarteditor2/css/smart_editor2.css +++ b/plugin/editor/smarteditor2/css/smart_editor2.css @@ -3,6 +3,7 @@ /* COMMON */ body,#smart_editor2,#smart_editor2 p,#smart_editor2 h1,#smart_editor2 h2,#smart_editor2 h3,#smart_editor2 h4,#smart_editor2 h5,#smart_editor2 h6,#smart_editor2 ul,#smart_editor2 ol,#smart_editor2 li,#smart_editor2 dl,#smart_editor2 dt,#smart_editor2 dd,#smart_editor2 table,#smart_editor2 th,#smart_editor2 td,#smart_editor2 form,#smart_editor2 fieldset,#smart_editor2 legend,#smart_editor2 input,#smart_editor2 textarea,#smart_editor2 button,#smart_editor2 select{margin:0;padding:0} #smart_editor2,#smart_editor2 h1,#smart_editor2 h2,#smart_editor2 h3,#smart_editor2 h4,#smart_editor2 h5,#smart_editor2 h6,#smart_editor2 input,#smart_editor2 textarea,#smart_editor2 select,#smart_editor2 table,#smart_editor2 button{font-family:'돋움',Dotum,Helvetica,sans-serif;font-size:12px;color:#666} +#smart_editor2{background:#fff} #smart_editor2 span,#smart_editor2 em{font-size:12px} #smart_editor2 em,#smart_editor2 address{font-style:normal} #smart_editor2 img,#smart_editor2 fieldset{border:0} diff --git a/plugin/kcaptcha/kcaptcha.lib.php b/plugin/kcaptcha/kcaptcha.lib.php index 7fc5309a7..50a3cef69 100644 --- a/plugin/kcaptcha/kcaptcha.lib.php +++ b/plugin/kcaptcha/kcaptcha.lib.php @@ -248,9 +248,9 @@ function captcha_html($class="captcha") if (is_mobile()) $html .= ''; //$html .= "\n".''; $html .= "\n".''; + $html .= ''; if (!is_mobile()) $html .= "\n".''; $html .= "\n".''; - $html .= ''; $html .= "\n".'자동등록방지 숫자를 순서대로 입력하세요.'; $html .= "\n".''; return $html; diff --git a/plugin/recaptcha/_common.php b/plugin/recaptcha/_common.php new file mode 100644 index 000000000..14553420b --- /dev/null +++ b/plugin/recaptcha/_common.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/plugin/recaptcha/captcha.lib.php b/plugin/recaptcha/captcha.lib.php new file mode 100644 index 000000000..e67ee596c --- /dev/null +++ b/plugin/recaptcha/captcha.lib.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/plugin/recaptcha/recaptcha.class.php b/plugin/recaptcha/recaptcha.class.php new file mode 100644 index 000000000..781c13d3c --- /dev/null +++ b/plugin/recaptcha/recaptcha.class.php @@ -0,0 +1,147 @@ +secret = $secret; + } + + public function get_content($url, $data=array()) { + + $curlsession = curl_init(); + curl_setopt ($curlsession, CURLOPT_URL, $url); + curl_setopt ($curlsession, CURLOPT_POST, 1); + curl_setopt ($curlsession, CURLOPT_POSTFIELDS, http_build_query($data, '', '&')); + curl_setopt ($curlsession, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded')); + curl_setopt ($curlsession, CURLINFO_HEADER_OUT, false); + curl_setopt ($curlsession, CURLOPT_HEADER, false); + curl_setopt ($curlsession, CURLOPT_RETURNTRANSFER, 1); + curl_setopt ($curlsession, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt ($curlsession, CURLOPT_TIMEOUT, 3); + + $response = curl_exec($curlsession); + $cinfo = curl_getinfo($curlsession); + curl_close($curlsession); + + if ($cinfo['http_code'] != 200){ + return ''; + } + return $response; + } + + /** + * Submits an HTTP GET to a reCAPTCHA server. + * + * @param string $path url path to recaptcha server. + * @param array $data array of parameters to be sent. + * + * @return array response + */ + private function submit($url, $data) + { + $response = $this->get_content($url, $data); + return $response; + } + + /** + * Calls the reCAPTCHA siteverify API to verify whether the user passes + * CAPTCHA test. + * + * @param string $remoteIp IP address of end user. + * @param string $response response string from recaptcha verification. + * + * @return ReCaptchaResponse_v + */ + public function verify($response, $remoteIp = null) + { + // Discard empty solution submissions + if ($response == null || strlen($response) == 0) { + $recaptchaResponse = new ReCaptchaResponse_v(); + $recaptchaResponse->success = false; + $recaptchaResponse->errorCodes = 'missing-input'; + return $recaptchaResponse; + } + $getResponse = $this->submit( + self::$_siteVerifyUrl, + array ( + 'secret' => $this->secret, + 'remoteip' => $remoteIp, + 'version' => self::VERSION, + 'response' => $response + ) + ); + $answers = $getResponse ? json_decode($getResponse, true) : array(); + $recaptchaResponse = new ReCaptchaResponse_v(); + if (isset($answers['success']) && $answers['success'] == true) { + $recaptchaResponse->success = true; + } else { + $recaptchaResponse->success = false; + $recaptchaResponse->errorCodes = isset($answers['error-codes']) ? $answers['error-codes'] : 'http_error'; + } + return $recaptchaResponse; + } +} +?> \ No newline at end of file diff --git a/plugin/recaptcha/recaptcha.js b/plugin/recaptcha/recaptcha.js new file mode 100644 index 000000000..76dfc32b5 --- /dev/null +++ b/plugin/recaptcha/recaptcha.js @@ -0,0 +1,9 @@ +function chk_captcha() +{ + if ( ! jQuery('#g-recaptcha-response').val()) { + alert("자동등록방지를 반드시 체크해 주세요."); + return false; + } + + return true; +} \ No newline at end of file diff --git a/plugin/recaptcha/recaptcha.user.lib.php b/plugin/recaptcha/recaptcha.user.lib.php new file mode 100644 index 000000000..8a5c631ba --- /dev/null +++ b/plugin/recaptcha/recaptcha.user.lib.php @@ -0,0 +1,51 @@ +'; + $html .= ''; + $html .= ''; + $html .= '
'; + $html .= ''; + + return $html; +} + +// 캡챠 사용시 자바스크립트에서 입력된 캡챠를 검사함 +function chk_captcha_js() +{ + return "if (!chk_captcha()) return false;\n"; +} + +function chk_captcha(){ + + global $config; + + $resp = null; + + if ( isset($_POST["g-recaptcha-response"]) && !empty($_POST["g-recaptcha-response"]) ) { + + $reCaptcha = new ReCaptcha_GNU( $config['cf_recaptcha_secret_key'] ); + + $resp = $reCaptcha->verify($_POST["g-recaptcha-response"], $_SERVER["REMOTE_ADDR"]); + } + + if( ! $resp ){ + return false; + } + + if ($resp != null && $resp->success) { + return true; + } + + return false; +} +?> \ No newline at end of file diff --git a/plugin/sns/icon/facebook.png b/plugin/sns/icon/facebook.png index b9d733963ba81f9914f5f7ed1e08d79a2089b9ad..cf737d63396897c71b9d50974a9f0d1193176c2c 100644 GIT binary patch literal 3123 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 N002ovPDHLkV1j_o#n%7; literal 1052 zcmV+%1mpXOP)k{n-9;=ovBtPYSud3&m*Nk zbFJOH{@3z3WAq0~%IJ+#C!aX~$U+^7Huyi!k%g!@U8$bG{L>Yurfapgqqu}`e*6>3 zFa^YYQhCvAM&av{^$~ux%LkJNz?Dm8ut{LOtwr%oSl#&<>H9X5R6-Amv zr{ODyz5TTqL8}LHc=`DSJXEg)U=&IGyVl41pZvgg0nhSGWeGG4m~;CFEj1QXgueCq z2|RgZ4h(p>1!IKe)pclV;e_OzgJ&vZ4Z)mrc5nN5rq7lapPR>tV|Btt(2anU^4Y-f zbAEWH@*mtwv0saoz_Z8eyl%`xEN-oJ@%=CVgm2rOLD=KpgJK&L+j}bZ8_UHkDig)9 zPfCfezq^6=KKeFztmtj2kl9r2f0grU?Z}rMnZPk%B(%1{jwp&Y*OnS>^tm?413k@* z3rmU-Pi<~O&ER$?!RKFH=S_e(8s()5 z88*((?X}TQG+sJ=6vvMqB9O{ci!^0*3WKK~ujABH5AvN^(YTGzE?s4&2*k`1&kKr0 z0Mk3M-CKG#&TStQW6WkSCDnWAfA@Oxgu__zxGcOCQtYhfD_2)T4r?kswlE!`E)Y@1e8wtyio_;JH4P}*FIrGg}|gK)yr@LXNQ~{aEoVs(2=C#&gq<(niZfg&1M~TH!Axo2~)|!p8 zjmFIjkImN6(T)w@?$U%KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0007WNkl4n1OyQH$D z_K@{rH@}IZq$$Oh1xc4B6_O&DbELYXzy#2|2F0`kpVP3xBJece{b#@<;1{qDI03X- z^Yg&Tq{z8!aLz|{Ba-$yRL~;nlB5+$i;{MyX@i44|fhFK#P1lPa+ZzqBn|CpS_k;)A z7X$l8#Tz^)Uk$mL*%&HZ@J6+a@tkzP)PQm1zGG}W1oO@6F9+q~VaMs0B$XoG-W>=2VFcVxNgbwb3(CcL zQ_s2Nrlc)C8%n8dWI)89G!9iYPINH93~mG0(kS`+wRRaxyX5bcG+QUR zawEP;Y9-i^r1^-%rX-z7x*W?z`2E%loC8joGQZ74`mexyW0PYcp9*zLF8e>25zGi? e1pjk_zXt%HYKInS(XVCz0000CEX<4w?T#gmHcI35fWu!5fNqh{o*;p-%lTa>X<JE^!- z20lAihR<(bGqCatFbL@wFt7nF2fBsf&-ZT(U!Oh!yXf87lMG*v9AMyN_z!md|8L(I z_#%861Y=Se?zY!5{M)mIfti~dB&7uqKrGtR7X4um6lSHH~%J5{+1?{D8^VB_RuP)W^UkhXPXU;&2bUq+yz z%)np)hBYHAJH%ihU}R>2im@>;0UZYrK#X5ry?lKx#$V+B$9D|OAVFxv{rmEXfyX<9 zL8GA)9B!YE>}PmVlh44+!O0+1-o(JJpv>_9*DnSpK_Lca0U?H8uU|4Se*MD0%+Aj6 z>iD5&00G4E_v>edf531Ax&I$1(jX3CVC3Lp_;%(P!;e=l88{^*8Q2w-85lUZfW8NM z7Z}n1#UvU2{r&}Z9mxC4{DKUBczA&pb1;16in+#k)gPFK_8GsSZ@DC_pU*8#o!1x=3Ffcs<1P~K5&=q2CJ`8_>iTochRzZe? zEdY8Ck{%Na!#`kZ0b`&VfB<6o|LZ4% zq=zrVn?rknnzu2q@$rK*#b02!^J(cY2pE|$JU@Ms;pOT@3~a#21^Wb;j=@m@48{*f z4>G)Z@|fYr)r$^tKuH-) zLoEdw{tO_17}se?z5<4+C=)O(vheaTsKz8QXvC+2Gwqee3Wn#)<}rZE5hi%S!~hN| zCZvK2&SrvG2r?KTfS5ozg@uisfd!bJ|K7XBzz&rB4~obiz!)|#2B+u0z>ENi)PK;3 z{f~%bDE${I{ujyz2q31vAVt6w@CStd{9$-~`6963`Ofh7#}5W^7cT~tyfScP{ec_K z05=4b=E3^G$@3qC2ATsB0|+3dU!a@^3?D9OIRXZZ2vH3J*4l=}bYH-lVs zB7=5g4+9f0tN!`<3+zIeE8*}z#1fbfKoB5+7%z4-ypptW6#Wm(eedqvX88W*B{*{U z6cibx&27LIfO6)GU0WG$wAO$Nb!K2TWFWIia20Ro8e>*I&7FV2}J@_6e;hX0>G z0t?{Z5J{kBeiL&By~0Wc5e+S1iN?b4X!!z$+Y>t&Sb(l)0%io5K@5!W)Buk>fB<4P zyLP7P`L1mo%)ng71T0rsfJu&-1&Dz~{23FEj2zH5V08;L_3wXRl>dXIV{lM_911CN9|4_S3=m)dx(R|hk4~Wg P00000NkvXXu0mjfN&e-$ diff --git a/plugin/sns/icon/kakaotalk.png b/plugin/sns/icon/kakaotalk.png index 78fe26ca7a44efcab004d7ae4fb634ad270f83f2..07de029ef9d9dcace55ab5230933d487e60b2634 100644 GIT binary patch literal 4179 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 literal 1532 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 XP5mJXa!~Dz00000NkvXXu0mjfpzu3< literal 1030 zcmV+h1o``kP)=olesJ~Gzdo{Zz1_IA-mYUmSI`Cs z=7h2)DS)+h4eJZdTQaLwI)g$J)Jz|ep+G=0SZg8ZrqJev;QW}TW!H!)>AjLW+Ik-% zns#fahmSvdh@FEWZoGF9@4vAI^@4ErlbJ%5rGq*+wMrP^%ZJbK-QF>tRTF&r%@gEf zjnbSx^S_N)%#=!(bYrGg@UlfxLEOqvi$lm*BB@HU#QQkCsLu%+jx_AdAAHYDsf_lD zC;uvI5R%7L$t{GXNjD#CdCzCKyn;?6#+#RyAnDmDoD~yOvTSF^M6oJ;GAdF!u%dyx z-~M?{E?a2{ZZM$n>sMYu&dT8n?yX=b$O=0Fn*fI7g#;C0h$^+0R~DYD5wD;-RM^cY z!sz{zxgA)W(s^~Fn2T>Hk2WG3B*!j5$^kuhu@#rvF|Ka3ajY~6@>q^1Go|LjIbZxA zMQKTYz+r|WuNPv0d)L<768~#s=B2_=nd!z7fd!vbVDxK#% ze8Tcp&@7x^1{buHP9pHuS`GJqAAkg-b)ks^I;0*SYQNrzq4~H#6z919S{oH?8@=vF@d#QGs_>QxriHpKlMGytE!8ZQzW@RYi57-O0r&OXg5iBe>{Iif2*S z62hJyn!AG`uB~Rcvb2Z?Pe(4Vp*VYztz;7KE?XQGIZa4Vc$QPFlnPw|_=udXF!i^4 zeYC0(Zd}Z$V2K+$-5>!*l;5qR9wmwwz%eDC*;Zo8!#wYnS}oI;1i8{UbE43-HymSU zaD0B)Il>2%6vA818!3`xsRA?Q{y+Use%O9=_x1HvG~z1Cc|S|6mbNmnz)VS7aP#M< zyTM+sce9?Roj{2BK279npOkj{`EB|FZT>6302R|;hYG@@=Z A0RR91 diff --git a/plugin/sns/view.sns.skin.php b/plugin/sns/view.sns.skin.php index aa1cc0ba4..a798cfd0e 100644 --- a/plugin/sns/view.sns.skin.php +++ b/plugin/sns/view.sns.skin.php @@ -30,12 +30,15 @@ $gplus_url = $sns_send.'&sns=gplus'; Kakao.init(""); +
+
    -
  • 페이스북으로 보내기
  • -
  • 트위터로 보내기
  • -
  • 구글플러스로 보내기
  • +
  • 트위터로 보내기
  • +
  • 페이스북으로 보내기
  • +
  • 구글플러스로 보내기
  • -
  • 카카오톡으로 보내기
  • +
  • 카카오톡으로 보내기
+
diff --git a/plugin/sns/view_comment_write.sns.skin.php b/plugin/sns/view_comment_write.sns.skin.php index 75584ba35..d58b3d08b 100644 --- a/plugin/sns/view_comment_write.sns.skin.php +++ b/plugin/sns/view_comment_write.sns.skin.php @@ -30,17 +30,16 @@ if ($config['cf_facebook_appid']) { } } - echo '
  • '; + echo '
  • '; if ($facebook_user) { echo ''; echo ''; echo ''; } else { $facebook_url = $facebook->getLoginUrl(array("redirect_uri"=>G5_SNS_URL."/facebook/callback.php", "scope"=>"publish_stream,read_stream,offline_access", "display"=>"popup")); - - echo ''; - echo ''; echo ''; + echo ''; + echo ''; echo ''; } echo '
  • '; @@ -90,15 +89,15 @@ if ($config['cf_twitter_key']) { } } - echo '
  • '; + echo '
  • '; if ($twitter_user) { echo ''; echo ''; echo ''; } else { - echo ''; echo ''; echo ''; + echo ''; echo ''; } echo '
  • '; diff --git a/plugin/social/Hybrid/Auth.php b/plugin/social/Hybrid/Auth.php new file mode 100644 index 000000000..5642b5091 --- /dev/null +++ b/plugin/social/Hybrid/Auth.php @@ -0,0 +1,414 @@ +getSessionData()); + Hybrid_Logger::info("Hybrid_Auth initialize: check if any error is stored on the endpoint..."); + + if (Hybrid_Error::hasError()) { + $m = Hybrid_Error::getErrorMessage(); + $c = Hybrid_Error::getErrorCode(); + $p = Hybrid_Error::getErrorPrevious(); + + Hybrid_Logger::error("Hybrid_Auth initialize: A stored Error found, Throw an new Exception and delete it from the store: Error#$c, '$m'"); + + Hybrid_Error::clearError(); + + // try to provide the previous if any + // Exception::getPrevious (PHP 5 >= 5.3.0) http://php.net/manual/en/exception.getprevious.php + if (version_compare(PHP_VERSION, '5.3.0', '>=') && ($p instanceof Exception)) { + throw new Exception($m, $c, $p); + } else { + throw new Exception($m, $c); + } + } + + Hybrid_Logger::info("Hybrid_Auth initialize: no error found. initialization succeed."); + } + + /** + * Hybrid storage system accessor + * + * Users sessions are stored using HybridAuth storage system ( HybridAuth 2.0 handle PHP Session only) and can be accessed directly by + * Hybrid_Auth::storage()->get($key) to retrieves the data for the given key, or calling + * Hybrid_Auth::storage()->set($key, $value) to store the key => $value set. + * + * @return Hybrid_Storage + */ + public static function storage() { + return Hybrid_Auth::$store; + } + + /** + * Get hybridauth session data + * @return string|null + */ + function getSessionData() { + return Hybrid_Auth::storage()->getSessionData(); + } + + /** + * Restore hybridauth session data + * + * @param string $sessiondata Serialized session data + * @retun void + */ + function restoreSessionData($sessiondata = null) { + Hybrid_Auth::storage()->restoreSessionData($sessiondata); + } + + /** + * Try to authenticate the user with a given provider. + * + * If the user is already connected we just return and instance of provider adapter, + * ELSE, try to authenticate and authorize the user with the provider. + * + * $params is generally an array with required info in order for this provider and HybridAuth to work, + * like : + * hauth_return_to: URL to call back after authentication is done + * openid_identifier: The OpenID identity provider identifier + * google_service: can be "Users" for Google user accounts service or "Apps" for Google hosted Apps + * + * @param string $providerId ID of the provider + * @param array $params Params + * @return + */ + public static function authenticate($providerId, $params = null) { + Hybrid_Logger::info("Enter Hybrid_Auth::authenticate( $providerId )"); + + if (!Hybrid_Auth::storage()->get("hauth_session.$providerId.is_logged_in")) { + // if user not connected to $providerId then try setup a new adapter and start the login process for this provider + Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User not connected to the provider. Try to authenticate.."); + $provider_adapter = Hybrid_Auth::setup($providerId, $params); + $provider_adapter->login(); + } else { + // else, then return the adapter instance for the given provider + Hybrid_Logger::info("Hybrid_Auth::authenticate( $providerId ), User is already connected to this provider. Return the adapter instance."); + return Hybrid_Auth::getAdapter($providerId); + } + } + + /** + * Return the adapter instance for an authenticated provider + * + * @param string $providerId ID of the provider + * @return Hybrid_Provider_Adapter + */ + public static function getAdapter($providerId = null) { + Hybrid_Logger::info("Enter Hybrid_Auth::getAdapter( $providerId )"); + return Hybrid_Auth::setup($providerId); + } + + /** + * Setup an adapter for a given provider + * + * @param string $providerId ID of the provider + * @param array $params Adapter params + * @return Hybrid_Provider_Adapter + */ + public static function setup($providerId, $params = null) { + Hybrid_Logger::debug("Enter Hybrid_Auth::setup( $providerId )", $params); + + if (!$params) { + $params = Hybrid_Auth::storage()->get("hauth_session.$providerId.id_provider_params"); + + Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ), no params given. Trying to get the stored for this provider.", $params); + } + + if (!$params) { + $params = array(); + Hybrid_Logger::info("Hybrid_Auth::setup( $providerId ), no stored params found for this provider. Initialize a new one for new session"); + } + + if (is_array($params) && !isset($params["hauth_return_to"])) { + $params["hauth_return_to"] = Hybrid_Auth::getCurrentUrl(); + Hybrid_Logger::debug("Hybrid_Auth::setup( $providerId ). HybridAuth Callback URL set to: ", $params["hauth_return_to"]); + } + + # instantiate a new IDProvider Adapter + $provider = new Hybrid_Provider_Adapter(); + $provider->factory($providerId, $params); + return $provider; + } + + /** + * Check if the current user is connected to a given provider + * + * @param string $providerId ID of the provider + * @return bool + */ + public static function isConnectedWith($providerId) { + return (bool) Hybrid_Auth::storage()->get("hauth_session.{$providerId}.is_logged_in"); + } + + /** + * Return array listing all authenticated providers + * @return array + */ + public static function getConnectedProviders() { + $idps = array(); + + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if (Hybrid_Auth::isConnectedWith($idpid)) { + $idps[] = $idpid; + } + } + + return $idps; + } + + /** + * Return array listing all enabled providers as well as a flag if you are connected + * + * + * array( + * 'Facebook' => array( + * 'connected' => true + * ) + * ) + * + * @return array + */ + public static function getProviders() { + $idps = array(); + + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if ($params['enabled']) { + $idps[$idpid] = array('connected' => false); + + if (Hybrid_Auth::isConnectedWith($idpid)) { + $idps[$idpid]['connected'] = true; + } + } + } + + return $idps; + } + + /** + * A generic function to logout all connected provider at once + * @return void + */ + public static function logoutAllProviders() { + $idps = Hybrid_Auth::getConnectedProviders(); + + foreach ($idps as $idp) { + $adapter = Hybrid_Auth::getAdapter($idp); + $adapter->logout(); + } + } + + /** + * Utility function, redirect to a given URL with php header or using javascript location.href + * + * @param string $url URL to redirect to + * @param string $mode PHP|JS + */ + public static function redirect($url, $mode = "PHP") { + if(!$mode){ + $mode = 'PHP'; + } + Hybrid_Logger::info("Enter Hybrid_Auth::redirect( $url, $mode )"); + + // Ensure session is saved before sending response, see https://github.com/symfony/symfony/pull/12341 + if ((PHP_VERSION_ID >= 50400 && PHP_SESSION_ACTIVE === session_status()) || (PHP_VERSION_ID < 50400 && isset($_SESSION) && session_id())) { + session_write_close(); + } + + if ($mode == "PHP") { + header("Location: $url"); + } elseif ($mode == "JS") { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo 'Redirecting, please wait...'; + echo ''; + echo ''; + } + + die(); + } + + /** + * Utility function, return the current url + * + * @param bool $request_uri true to get $_SERVER['REQUEST_URI'], false for $_SERVER['PHP_SELF'] + * @return string + */ + public static function getCurrentUrl($request_uri = true) { + if (php_sapi_name() == 'cli') { + return ''; + } + + $protocol = 'http://'; + + if ((isset($_SERVER['HTTPS']) && ( $_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1 )) + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) + { + $protocol = 'https://'; + } + + $url = $protocol . $_SERVER['HTTP_HOST']; + + if ($request_uri) { + $url .= $_SERVER['REQUEST_URI']; + } else { + $url .= $_SERVER['PHP_SELF']; + } + + // return current url + return $url; + } + +} diff --git a/plugin/social/Hybrid/Endpoint.php b/plugin/social/Hybrid/Endpoint.php new file mode 100644 index 000000000..7813fee05 --- /dev/null +++ b/plugin/social/Hybrid/Endpoint.php @@ -0,0 +1,222 @@ +here we need to parse $_SERVER[QUERY_STRING] + $request = $_REQUEST; + if (isset($_SERVER["QUERY_STRING"]) && strrpos($_SERVER["QUERY_STRING"], '?')) { + $_SERVER["QUERY_STRING"] = str_replace("?", "&", $_SERVER["QUERY_STRING"]); + parse_str($_SERVER["QUERY_STRING"], $request); + } + } + + // Setup request variable + $this->request = $request; + + // If openid_policy requested, we return our policy document + if (isset($this->request["get"]) && $this->request["get"] == "openid_policy") { + $this->processOpenidPolicy(); + } + + // If openid_xrds requested, we return our XRDS document + if (isset($this->request["get"]) && $this->request["get"] == "openid_xrds") { + $this->processOpenidXRDS(); + } + + // If we get a hauth.start + if (isset($this->request["hauth_start"]) && $this->request["hauth_start"]) { + $this->processAuthStart(); + } + // Else if hauth.done + elseif (isset($this->request["hauth_done"]) && $this->request["hauth_done"]) { + $this->processAuthDone(); + } + // Else we advertise our XRDS document, something supposed to be done from the Realm URL page + else { + $this->processOpenidRealm(); + } + } + + /** + * Process the current request + * + * @param array $request The current request parameters. Leave as null to default to use $_REQUEST. + * @return Hybrid_Endpoint + */ + public static function process($request = null) { + // Trick for PHP 5.2, because it doesn't support late static binding + $class = function_exists('get_called_class') ? get_called_class() : __CLASS__; + new $class($request); + } + + /** + * Process OpenID policy request + * @return void + */ + protected function processOpenidPolicy() { + $output = file_get_contents(dirname(__FILE__) . "/resources/openid_policy.html"); + print $output; + die(); + } + + /** + * Process OpenID XRDS request + * @return void + */ + protected function processOpenidXRDS() { + header("Content-Type: application/xrds+xml"); + + $output = str_replace("{RETURN_TO_URL}", str_replace( + array("<", ">", "\"", "'", "&"), array("<", ">", """, "'", "&"), Hybrid_Auth::getCurrentUrl(false) + ), file_get_contents(dirname(__FILE__) . "/resources/openid_xrds.xml")); + print $output; + die(); + } + + /** + * Process OpenID realm request + * @return void + */ + protected function processOpenidRealm() { + $output = str_replace("{X_XRDS_LOCATION}", htmlentities(Hybrid_Auth::getCurrentUrl(false), ENT_QUOTES, 'UTF-8') + . "?get=openid_xrds&v=" + . Hybrid_Auth::$version, file_get_contents(dirname(__FILE__) . "/resources/openid_realm.html")); + print $output; + die(); + } + + /** + * Define: endpoint step 3 + * @return void + * @throws Hybrid_Exception + */ + protected function processAuthStart() { + $this->authInit(); + + $provider_id = trim(strip_tags($this->request["hauth_start"])); + + // check if page accessed directly + if (!Hybrid_Auth::storage()->get("hauth_session.$provider_id.hauth_endpoint")) { + Hybrid_Logger::error("Endpoint: hauth_endpoint parameter is not defined on hauth_start, halt login process!"); + + throw new Hybrid_Exception("You cannot access this page directly."); + } + + // define:hybrid.endpoint.php step 2. + $hauth = Hybrid_Auth::setup($provider_id); + + // if REQUESTed hauth_idprovider is wrong, session not created, etc. + if (!$hauth) { + Hybrid_Logger::error("Endpoint: Invalid parameter on hauth_start!"); + throw new Hybrid_Exception("Invalid parameter! Please return to the login page and try again."); + } + + try { + Hybrid_Logger::info("Endpoint: call adapter [{$provider_id}] loginBegin()"); + + $hauth->adapter->loginBegin(); + } catch (Exception $e) { + Hybrid_Logger::error("Exception:" . $e->getMessage(), $e); + Hybrid_Error::setError($e->getMessage(), $e->getCode(), $e->getTraceAsString(), $e->getPrevious()); + + $hauth->returnToCallbackUrl(); + } + + die(); + } + + /** + * Define: endpoint step 3.1 and 3.2 + * @return void + * @throws Hybrid_Exception + */ + protected function processAuthDone() { + $this->authInit(); + + $provider_id = trim(strip_tags($this->request["hauth_done"])); + + $hauth = Hybrid_Auth::setup($provider_id); + + if (!$hauth) { + Hybrid_Logger::error("Endpoint: Invalid parameter on hauth_done!"); + + $hauth->adapter->setUserUnconnected(); + + throw new Hybrid_Exception("Invalid parameter! Please return to the login page and try again."); + } + + try { + Hybrid_Logger::info("Endpoint: call adapter [{$provider_id}] loginFinish() "); + $hauth->adapter->loginFinish(); + } catch (Exception $e) { + Hybrid_Logger::error("Exception:" . $e->getMessage(), $e); + Hybrid_Error::setError($e->getMessage(), $e->getCode(), $e->getTraceAsString(), $e->getPrevious()); + + $hauth->adapter->setUserUnconnected(); + } + + Hybrid_Logger::info("Endpoint: job done. return to callback url."); + + $hauth->returnToCallbackUrl(); + die(); + } + + /** + * Initializes authentication + * @throws Hybrid_Exception + */ + protected function authInit() { + if (!$this->initDone) { + $this->initDone = true; + + // Init Hybrid_Auth + try { + if (!class_exists("Hybrid_Storage", false)) { + require_once realpath(dirname(__FILE__)) . "/Storage.php"; + } + if (!class_exists("Hybrid_Exception", false)) { + require_once realpath(dirname(__FILE__)) . "/Exception.php"; + } + if (!class_exists("Hybrid_Logger", false)) { + require_once realpath(dirname(__FILE__)) . "/Logger.php"; + } + + $storage = new Hybrid_Storage(); + + // Check if Hybrid_Auth session already exist + if (!$storage->config("CONFIG")) { + throw new Hybrid_Exception("You cannot access this page directly."); + } + + Hybrid_Auth::initialize($storage->config("CONFIG")); + } catch (Exception $e) { + Hybrid_Logger::error("Endpoint: Error while trying to init Hybrid_Auth: " . $e->getMessage()); + throw new Hybrid_Exception( "Endpoint: Error while trying to init Hybrid_Auth: " . $e->getMessage(), $e->getCode(), $e ); + } + } + } + +} diff --git a/plugin/social/Hybrid/Error.php b/plugin/social/Hybrid/Error.php new file mode 100644 index 000000000..e9c319c68 --- /dev/null +++ b/plugin/social/Hybrid/Error.php @@ -0,0 +1,88 @@ +set("hauth_session.error.status", 1); + Hybrid_Auth::storage()->set("hauth_session.error.message", $message); + Hybrid_Auth::storage()->set("hauth_session.error.code", $code); + Hybrid_Auth::storage()->set("hauth_session.error.trace", $trace); + Hybrid_Auth::storage()->set("hauth_session.error.previous", $previous); + } + + /** + * Clear the last error + * @return void + */ + public static function clearError() { + Hybrid_Logger::info("Enter Hybrid_Error::clearError()"); + + Hybrid_Auth::storage()->delete("hauth_session.error.status"); + Hybrid_Auth::storage()->delete("hauth_session.error.message"); + Hybrid_Auth::storage()->delete("hauth_session.error.code"); + Hybrid_Auth::storage()->delete("hauth_session.error.trace"); + Hybrid_Auth::storage()->delete("hauth_session.error.previous"); + } + + /** + * Checks to see if there is a an error. + * @return boolean true if there is an error. + */ + public static function hasError() { + return (bool) Hybrid_Auth::storage()->get("hauth_session.error.status"); + } + + /** + * Return error message + * @return string + */ + public static function getErrorMessage() { + return Hybrid_Auth::storage()->get("hauth_session.error.message"); + } + + /** + * Return error code + * @return int + */ + public static function getErrorCode() { + return Hybrid_Auth::storage()->get("hauth_session.error.code"); + } + + /** + * Return string detailed error backtrace as string + * @return string + */ + public static function getErrorTrace() { + return Hybrid_Auth::storage()->get("hauth_session.error.trace"); + } + + /** + * Detailed error backtrace as string + * @return string + */ + public static function getErrorPrevious() { + return Hybrid_Auth::storage()->get("hauth_session.error.previous"); + } + +} diff --git a/plugin/social/Hybrid/Exception.php b/plugin/social/Hybrid/Exception.php new file mode 100644 index 000000000..8c8c2d1fa --- /dev/null +++ b/plugin/social/Hybrid/Exception.php @@ -0,0 +1,17 @@ +format(DATE_ATOM), + $message, + print_r($object, true) . PHP_EOL, + )), FILE_APPEND + ); + } + } + + /** + * Logs an info message + * + * @param string $message Info message + * @return void + */ + public static function info($message) { + if (in_array(Hybrid_Auth::$config["debug_mode"], array(true, 'info'), true)) { + $dt = new DateTime('now', new DateTimeZone( 'UTC' )); + file_put_contents(Hybrid_Auth::$config["debug_file"], implode(' -- ', array( + "INFO", + $_SERVER['REMOTE_ADDR'], + $dt->format(DATE_ATOM), + $message . PHP_EOL, + )), FILE_APPEND); + } + } + + /** + * Logs an error message with an object dump + * + * @param string $message Error message + * @param stdClass $object Object being debugged + * @return void + */ + public static function error($message, $object = null) { + if (isset(Hybrid_Auth::$config["debug_mode"]) && in_array(Hybrid_Auth::$config["debug_mode"], array(true, 'info', 'error'), true)) { + $dt = new DateTime('now', new DateTimeZone( 'UTC' )); + file_put_contents(Hybrid_Auth::$config["debug_file"], implode(' -- ', array( + 'ERROR', + $_SERVER['REMOTE_ADDR'], + $dt->format(DATE_ATOM), + $message, + print_r($object, true) . PHP_EOL + )), FILE_APPEND); + } + } + + /** + * Dumps the data in the way suitable to be output in log files for debug purposes + * + * @param mixed $data + * + * @return string + */ + public static function dumpData($data) { + return var_export($data, true); + } + +} diff --git a/plugin/social/Hybrid/Provider_Adapter.php b/plugin/social/Hybrid/Provider_Adapter.php new file mode 100644 index 000000000..aeeb30bb2 --- /dev/null +++ b/plugin/social/Hybrid/Provider_Adapter.php @@ -0,0 +1,340 @@ +id = $id; + $this->params = $params; + $this->id = $this->getProviderCiId($this->id); + $this->config = $this->getConfigById($this->id); + + # check the IDp id + if (!$this->id) { + throw new Exception("No provider ID specified.", 2); + } + + # check the IDp config + if (!$this->config) { + throw new Exception("Unknown Provider ID, check your configuration file.", 3); + } + + # check the IDp adapter is enabled + if (!$this->config["enabled"]) { + throw new Exception("The provider '{$this->id}' is not enabled.", 3); + } + + # include the adapter wrapper + if (isset($this->config["wrapper"]) && is_array($this->config["wrapper"])) { + if (isset($this->config["wrapper"]["path"])) { + require_once $this->config["wrapper"]["path"]; + } + + if (!class_exists($this->config["wrapper"]["class"])) { + throw new Exception("Unable to load the adapter class.", 3); + } + + $this->wrapper = $this->config["wrapper"]["class"]; + } else { + require_once Hybrid_Auth::$config["path_providers"] . $this->id . ".php"; + + $this->wrapper = "Hybrid_Providers_" . $this->id; + } + + # create the adapter instance, and pass the current params and config + $this->adapter = new $this->wrapper($this->id, $this->config, $this->params); + + return $this; + } + + /** + * Hybrid_Provider_Adapter::login(), prepare the user session and the authentication request + * for index.php + * @return void + * @throw Exception + */ + function login() { + Hybrid_Logger::info("Enter Hybrid_Provider_Adapter::login( {$this->id} ) "); + + if (!$this->adapter) { + throw new Exception("Hybrid_Provider_Adapter::login() should not directly used."); + } + + // clear all unneeded params + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.hauth_return_to"); + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.hauth_endpoint"); + Hybrid_Auth::storage()->delete("hauth_session.{$idpid}.id_provider_params"); + } + + // make a fresh start + $this->logout(); + + # get hybridauth base url + if (empty(Hybrid_Auth::$config["base_url"])) { + // the base url wasn't provide, so we must use the current + // url (which makes sense actually) + $url = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off' ? 'http' : 'https'; + $url .= '://' . $_SERVER['HTTP_HOST']; + $url .= $_SERVER['REQUEST_URI']; + $HYBRID_AUTH_URL_BASE = $url; + } else { + $HYBRID_AUTH_URL_BASE = Hybrid_Auth::$config["base_url"]; + } + + // make sure params is array + if (!is_array($this->params)) { + $this->params = array(); + } + + # we make use of session_id() as storage hash to identify the current user + # using session_regenerate_id() will be a problem, but .. + $this->params["hauth_token"] = session_id(); + + # set request timestamp + $this->params["hauth_time"] = time(); + + # for default HybridAuth endpoint url hauth_login_start_url + # auth.start required the IDp ID + # auth.time optional login request timestamp + if (!isset($this->params["login_start"]) ) { + $this->params["login_start"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.start={$this->id}&hauth.time={$this->params["hauth_time"]}"; + } + + # for default HybridAuth endpoint url hauth_login_done_url + # auth.done required the IDp ID + if (!isset($this->params["login_done"]) ) { + $this->params["login_done"] = $HYBRID_AUTH_URL_BASE . ( strpos($HYBRID_AUTH_URL_BASE, '?') ? '&' : '?' ) . "hauth.done={$this->id}"; + } + + # workaround to solve windows live authentication since microsoft disallowed redirect urls to contain any parameters + # http://mywebsite.com/path_to_hybridauth/?hauth.done=Live will not work + if ($this->id=="Live") { + $this->params["login_done"] = $HYBRID_AUTH_URL_BASE."live.php"; + } + + # Workaround to fix broken callback urls for the Facebook OAuth client + if ($this->adapter->useSafeUrls) { + $this->params['login_done'] = str_replace('hauth.done', 'hauth_done', $this->params['login_done']); + } + + if (isset($this->params["hauth_return_to"])) { + Hybrid_Auth::storage()->set("hauth_session.{$this->id}.hauth_return_to", $this->params["hauth_return_to"]); + } + if (isset($this->params["login_done"])) { + Hybrid_Auth::storage()->set("hauth_session.{$this->id}.hauth_endpoint", $this->params["login_done"]); + } + Hybrid_Auth::storage()->set("hauth_session.{$this->id}.id_provider_params", $this->params); + + // store config to be used by the end point + Hybrid_Auth::storage()->config("CONFIG", Hybrid_Auth::$config); + + // move on + Hybrid_Logger::debug("Hybrid_Provider_Adapter::login( {$this->id} ), redirect the user to login_start URL."); + + // redirect + if (empty($this->params["redirect_mode"])) { + Hybrid_Auth::redirect($this->params["login_start"]); + } else { + Hybrid_Auth::redirect($this->params["login_start"],$this->params["redirect_mode"]); + } + } + + /** + * Let hybridauth forget all about the user for the current provider + * @return bool + */ + function logout() { + $this->adapter->logout(); + } + + // -------------------------------------------------------------------- + + /** + * Return true if the user is connected to the current provider + * @return bool + */ + public function isUserConnected() { + return $this->adapter->isUserConnected(); + } + + // -------------------------------------------------------------------- + + /** + * Call adapter methods defined in the adapter model: + * getUserProfile() + * getUserContacts() + * getUserActivity() + * setUserStatus() + * + * @param string $name Method name + * @param array $arguments Call arguments + * @return mixed + * @throws Exception + */ + public function __call($name, $arguments) { + Hybrid_Logger::info("Enter Hybrid_Provider_Adapter::$name(), Provider: {$this->id}"); + + if (!$this->isUserConnected()) { + throw new Exception("User not connected to the provider {$this->id}.", 7); + } + + if (!method_exists($this->adapter, $name)) { + throw new Exception("Call to undefined function Hybrid_Providers_{$this->id}::$name()."); + } + + return call_user_func_array(array($this->adapter, $name), $arguments); + } + + /** + * If the user is connected, then return the access_token and access_token_secret + * if the provider api use oauth + * + * + * array( + * 'access_token' => '', + * 'access_token_secret' => '', + * 'refresh_token' => '', + * 'expires_in' => '', + * 'expires_at' => '', + * ) + * + * @return array + */ + public function getAccessToken() { + if (!$this->adapter->isUserConnected()) { + Hybrid_Logger::error("User not connected to the provider."); + throw new Exception("User not connected to the provider.", 7); + } + + return array( + "access_token" => $this->adapter->token("access_token"), // OAuth access token + "access_token_secret" => $this->adapter->token("access_token_secret"), // OAuth access token secret + "refresh_token" => $this->adapter->token("refresh_token"), // OAuth refresh token + "expires_in" => $this->adapter->token("expires_in"), // OPTIONAL. The duration in seconds of the access token lifetime + "expires_at" => $this->adapter->token("expires_at"), // OPTIONAL. Timestamp when the access_token expire. if not provided by the social api, then it should be calculated: expires_at = now + expires_in + ); + } + + /** + * Naive getter of the current connected IDp API client + * @return stdClass + * @throws Exception + */ + function api() { + if (!$this->adapter->isUserConnected()) { + Hybrid_Logger::error("User not connected to the provider."); + + throw new Exception("User not connected to the provider.", 7); + } + return $this->adapter->api; + } + + /** + * Redirect the user to hauth_return_to (the callback url) + * @return void + */ + function returnToCallbackUrl() { + // get the stored callback url + $callback_url = Hybrid_Auth::storage()->get("hauth_session.{$this->id}.hauth_return_to"); + + // if the user presses the back button in the browser and we already deleted the hauth_return_to from + // the session in the previous request, we will redirect to '/' instead of displaying a blank page. + if (!$callback_url) { + $callback_url = '/'; + } + + // remove some unneeded stored data + Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.hauth_return_to"); + Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.hauth_endpoint"); + Hybrid_Auth::storage()->delete("hauth_session.{$this->id}.id_provider_params"); + + // back to home + Hybrid_Auth::redirect($callback_url); + } + + /** + * Return the provider config by id + * + * @param string $id Config key + * @return mixed + */ + function getConfigById($id) { + if (isset(Hybrid_Auth::$config["providers"][$id])) { + return Hybrid_Auth::$config["providers"][$id]; + } + return null; + } + + /** + * Return the provider config by id; case insensitive + * + * @param string $id Provider id + * @return mixed + */ + function getProviderCiId($id) { + foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) { + if (strtolower($idpid) == strtolower($id)) { + return $idpid; + } + } + return null; + } + +} diff --git a/plugin/social/Hybrid/Provider_Model.php b/plugin/social/Hybrid/Provider_Model.php new file mode 100644 index 000000000..320cee9e1 --- /dev/null +++ b/plugin/social/Hybrid/Provider_Model.php @@ -0,0 +1,247 @@ +params = Hybrid_Auth::storage()->get("hauth_session.$providerId.id_provider_params"); + } else { + $this->params = $params; + } + + // idp id + $this->providerId = $providerId; + + // set HybridAuth endpoint for this provider + $this->endpoint = Hybrid_Auth::storage()->get("hauth_session.$providerId.hauth_endpoint"); + + // idp config + $this->config = $config; + + // new user instance + $this->user = new Hybrid_User(); + $this->user->providerId = $providerId; + + // initialize the current provider adapter + $this->initialize(); + + Hybrid_Logger::debug("Hybrid_Provider_Model::__construct( $providerId ) initialized. dump current adapter instance: ", serialize($this)); + } + + /** + * IDp wrappers initializer + * + * The main job of wrappers initializer is to performs (depend on the IDp api client it self): + * - include some libs needed by this provider, + * - check IDp key and secret, + * - set some needed parameters (stored in $this->params) by this IDp api client + * - create and setup an instance of the IDp api client on $this->api + * + * @return void + * @throws Exception + */ + abstract protected function initialize(); + + /** + * Begin login + * + * @return void + * @throws Exception + */ + abstract public function loginBegin(); + + /** + * Finish login + * @return void + * @throws Exception + */ + abstract public function loginFinish(); + + /** + * Generic logout, just erase current provider adapter stored data to let Hybrid_Auth all forget about it + * @return bool + */ + function logout() { + Hybrid_Logger::info("Enter [{$this->providerId}]::logout()"); + $this->clearTokens(); + return true; + } + + /** + * Grab the user profile from the IDp api client + * @return Hybrid_User_Profile + * @throws Exception + */ + function getUserProfile() { + Hybrid_Logger::error("HybridAuth do not provide users contacts list for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Load the current logged in user contacts list from the IDp api client + * @return Hybrid_User_Contact[] + * @throws Exception + */ + function getUserContacts() { + Hybrid_Logger::error("HybridAuth do not provide users contacts list for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return the user activity stream + * @return Hybrid_User_Activity[] + * @throws Exception + */ + function getUserActivity($stream) { + Hybrid_Logger::error("HybridAuth do not provide user's activity stream for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Set user status + * @return mixed Provider response + * @throws Exception + */ + function setUserStatus($status) { + Hybrid_Logger::error("HybridAuth do not provide user's activity stream for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return the user status + * @return mixed Provider response + * @throws Exception + */ + function getUserStatus($statusid) { + Hybrid_Logger::error("HybridAuth do not provide user's status for {$this->providerId} yet."); + throw new Exception("Provider does not support this feature.", 8); + } + + /** + * Return true if the user is connected to the current provider + * @return bool + */ + public function isUserConnected() { + return (bool) Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.is_logged_in"); + } + + /** + * Set user to connected + * @return void + */ + public function setUserConnected() { + Hybrid_Logger::info("Enter [{$this->providerId}]::setUserConnected()"); + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.is_logged_in", 1); + } + + /** + * Set user to unconnected + * @return void + */ + public function setUserUnconnected() { + Hybrid_Logger::info("Enter [{$this->providerId}]::setUserUnconnected()"); + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.is_logged_in", 0); + } + + /** + * Get or set a token + * @return string + */ + public function token($token, $value = null) { + if ($value === null) { + return Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.token.$token"); + } else { + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.token.$token", $value); + } + } + + /** + * Delete a stored token + * @return void + */ + public function deleteToken($token) { + Hybrid_Auth::storage()->delete("hauth_session.{$this->providerId}.token.$token"); + } + + /** + * Clear all existent tokens for this provider + * @return void + */ + public function clearTokens() { + Hybrid_Auth::storage()->deleteMatch("hauth_session.{$this->providerId}."); + } + +} diff --git a/plugin/social/Hybrid/Provider_Model_OAuth1.php b/plugin/social/Hybrid/Provider_Model_OAuth1.php new file mode 100644 index 000000000..6f5f239ae --- /dev/null +++ b/plugin/social/Hybrid/Provider_Model_OAuth1.php @@ -0,0 +1,174 @@ + "OK: Success!", + 304 => "Not Modified: There was no new data to return.", + 400 => "Bad Request: The request was invalid.", + 401 => "Unauthorized.", + 403 => "Forbidden: The request is understood, but it has been refused.", + 404 => "Not Found: The URI requested is invalid or the resource requested does not exists.", + 406 => "Not Acceptable.", + 500 => "Internal Server Error: Something is broken.", + 502 => "Bad Gateway.", + 503 => "Service Unavailable." + ); + + if (!$code && $this->api) { + $code = $this->api->http_code; + } + + if (isset($http_status_codes[$code])) { + return $code . " " . $http_status_codes[$code]; + } + } + + /** + * {@inheritdoc} + */ + function initialize() { + // 1 - check application credentials + if (!$this->config["keys"]["key"] || !$this->config["keys"]["secret"]) { + throw new Exception("Your application key and secret are required in order to connect to {$this->providerId}.", 4); + } + + // 2 - include OAuth lib and client + if (! class_exists('OAuthConsumer') ) { + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth.php"; + } + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth1Client.php"; + + // 3.1 - setup access_token if any stored + if ($this->token("access_token")) { + $this->api = new OAuth1Client( + $this->config["keys"]["key"], $this->config["keys"]["secret"], $this->token("access_token"), $this->token("access_token_secret") + ); + } + + // 3.2 - setup request_token if any stored, in order to exchange with an access token + elseif ($this->token("request_token")) { + $this->api = new OAuth1Client( + $this->config["keys"]["key"], $this->config["keys"]["secret"], $this->token("request_token"), $this->token("request_token_secret") + ); + } + + // 3.3 - instanciate OAuth client with client credentials + else { + $this->api = new OAuth1Client($this->config["keys"]["key"], $this->config["keys"]["secret"]); + } + + // Set curl proxy if exist + if (isset(Hybrid_Auth::$config["proxy"])) { + $this->api->curl_proxy = Hybrid_Auth::$config["proxy"]; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + $tokens = $this->api->requestToken($this->endpoint); + + // request tokens as received from provider + $this->request_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth token.", 5); + } + + $this->token("request_token", $tokens["oauth_token"]); + $this->token("request_token_secret", $tokens["oauth_token_secret"]); + + # redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens)); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + $oauth_token = (array_key_exists('oauth_token', $_REQUEST)) ? $_REQUEST['oauth_token'] : ""; + $oauth_verifier = (array_key_exists('oauth_verifier', $_REQUEST)) ? $_REQUEST['oauth_verifier'] : ""; + + if (!$oauth_token || !$oauth_verifier) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth verifier.", 5); + } + + // request an access token + $tokens = $this->api->accessToken($oauth_verifier); + + // access tokens as received from provider + $this->access_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + // we should have an access_token, or else, something has gone wrong + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // we no more need to store request tokens + $this->deleteToken("request_token"); + $this->deleteToken("request_token_secret"); + + // store access_token for later user + $this->token("access_token", $tokens['oauth_token']); + $this->token("access_token_secret", $tokens['oauth_token_secret']); + + // set user as logged in to the current provider + $this->setUserConnected(); + } + +} diff --git a/plugin/social/Hybrid/Provider_Model_OAuth2.php b/plugin/social/Hybrid/Provider_Model_OAuth2.php new file mode 100644 index 000000000..17a2103bd --- /dev/null +++ b/plugin/social/Hybrid/Provider_Model_OAuth2.php @@ -0,0 +1,184 @@ + "OK: Success!", + 304 => "Not Modified: There was no new data to return.", + 400 => "Bad Request: The request was invalid.", + 401 => "Unauthorized.", + 403 => "Forbidden: The request is understood, but it has been refused.", + 404 => "Not Found: The URI requested is invalid or the resource requested does not exists.", + 406 => "Not Acceptable.", + 500 => "Internal Server Error: Something is broken.", + 502 => "Bad Gateway.", + 503 => "Service Unavailable." + ); + + if (!$code && $this->api) { + $code = $this->api->http_code; + } + + if (isset($http_status_codes[$code])) { + return $code . " " . $http_status_codes[$code]; + } + } + + /** + * Adapter initializer + */ + function initialize() { + if (!$this->config["keys"]["id"] || !$this->config["keys"]["secret"]) { + throw new Exception("Your application id and secret are required in order to connect to {$this->providerId}.", 4); + } + + // override requested scope + if (isset($this->config["scope"]) && !empty($this->config["scope"])) { + $this->scope = $this->config["scope"]; + } + + // include OAuth2 client + require_once Hybrid_Auth::$config["path_libraries"] . "OAuth/OAuth2Client.php"; + + // create a new OAuth2 client instance + $this->api = new OAuth2Client($this->config["keys"]["id"], $this->config["keys"]["secret"], $this->endpoint, $this->compressed); + + // If we have an access token, set it + if ($this->token("access_token")) { + $this->api->access_token = $this->token("access_token"); + $this->api->refresh_token = $this->token("refresh_token"); + $this->api->access_token_expires_in = $this->token("expires_in"); + $this->api->access_token_expires_at = $this->token("expires_at"); + } + + // Set curl proxy if exist + if (isset(Hybrid_Auth::$config["proxy"])) { + $this->api->curl_proxy = Hybrid_Auth::$config["proxy"]; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + // redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl(array("scope" => $this->scope))); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + $error = (array_key_exists('error', $_REQUEST)) ? $_REQUEST['error'] : ""; + + // check for errors + if ($error) { + throw new Exception("Authentication failed! {$this->providerId} returned an error: $error", 5); + } + + // try to authenticate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + + try { + $this->api->authenticate($code); + } catch (Exception $e) { + throw new Exception("User profile request failed! {$this->providerId} returned an error: " . $e->getMessage(), 6); + } + + // check if authenticated + if (!$this->api->access_token) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + + // set user connected locally + $this->setUserConnected(); + } + + /** + * {@inheritdoc} + */ + function refreshToken() { + // have an access token? + if ($this->api->access_token) { + + // have to refresh? + if ($this->api->refresh_token && $this->api->access_token_expires_at) { + + // expired? + if ($this->api->access_token_expires_at <= time()) { + $response = $this->api->refreshToken(array("refresh_token" => $this->api->refresh_token)); + + if (!isset($response->access_token) || !$response->access_token) { + // set the user as disconnected at this point and throw an exception + $this->setUserUnconnected(); + + throw new Exception("The Authorization Service has return an invalid response while requesting a new access token. " . (string) $response->error); + } + + // set new access_token + $this->api->access_token = $response->access_token; + + if (isset($response->refresh_token)) + $this->api->refresh_token = $response->refresh_token; + + if (isset($response->expires_in)) { + $this->api->access_token_expires_in = $response->expires_in; + + // even given by some idp, we should calculate this + $this->api->access_token_expires_at = time() + $response->expires_in; + } + } + } + + // re store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + } + } + +} diff --git a/plugin/social/Hybrid/Provider_Model_OpenID.php b/plugin/social/Hybrid/Provider_Model_OpenID.php new file mode 100644 index 000000000..d3ec7cab5 --- /dev/null +++ b/plugin/social/Hybrid/Provider_Model_OpenID.php @@ -0,0 +1,170 @@ +public $openidIdentifier = ""; + * + * Hybrid_Provider_Model_OpenID use LightOpenID lib which can be found on + * Hybrid/thirdparty/OpenID/LightOpenID.php + */ +class Hybrid_Provider_Model_OpenID extends Hybrid_Provider_Model { + + /** + * Provider API client + * @var LightOpenID + */ + public $api = null; + + /** + * Openid provider identifier + * @var string + */ + public $openidIdentifier = ""; + + /** + * {@inheritdoc} + */ + function initialize() { + if (isset($this->params["openid_identifier"])) { + $this->openidIdentifier = $this->params["openid_identifier"]; + } + + // include LightOpenID lib + require_once Hybrid_Auth::$config["path_libraries"] . "OpenID/LightOpenID.php"; + + // An error was occurring when proxy wasn't set. Not sure where proxy was meant to be set/initialized. + Hybrid_Auth::$config['proxy'] = isset(Hybrid_Auth::$config['proxy']) ? Hybrid_Auth::$config['proxy'] : ''; + + $hostPort = parse_url(Hybrid_Auth::$config["base_url"], PHP_URL_PORT); + $hostUrl = parse_url(Hybrid_Auth::$config["base_url"], PHP_URL_HOST); + + // Check for port on url + if ($hostPort) { + $hostUrl .= ':' . $hostPort; + } + + $this->api = new LightOpenID($hostUrl, Hybrid_Auth::$config["proxy"]); + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + if (empty($this->openidIdentifier)) { + throw new Exception("OpenID adapter require the identity provider identifier 'openid_identifier' as an extra parameter.", 4); + } + + $this->api->identity = $this->openidIdentifier; + $this->api->returnUrl = $this->endpoint; + $this->api->required = array( + 'namePerson/first', + 'namePerson/last', + 'namePerson/friendly', + 'namePerson', + 'contact/email', + 'birthDate', + 'birthDate/birthDay', + 'birthDate/birthMonth', + 'birthDate/birthYear', + 'person/gender', + 'pref/language', + 'contact/postalCode/home', + 'contact/city/home', + 'contact/country/home', + 'media/image/default', + ); + + # redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authUrl()); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + # if user don't grant access of their data to your site, halt with an Exception + if ($this->api->mode == 'cancel') { + throw new Exception("Authentication failed! User has canceled authentication!", 5); + } + + # if something goes wrong + if (!$this->api->validate()) { + throw new Exception("Authentication failed. Invalid request received!", 5); + } + + # fetch received user data + $response = $this->api->getAttributes(); + + # store the user profile + $this->user->profile->identifier = $this->api->identity; + + $this->user->profile->firstName = (array_key_exists("namePerson/first", $response)) ? $response["namePerson/first"] : ""; + $this->user->profile->lastName = (array_key_exists("namePerson/last", $response)) ? $response["namePerson/last"] : ""; + $this->user->profile->displayName = (array_key_exists("namePerson", $response)) ? $response["namePerson"] : ""; + $this->user->profile->email = (array_key_exists("contact/email", $response)) ? $response["contact/email"] : ""; + $this->user->profile->language = (array_key_exists("pref/language", $response)) ? $response["pref/language"] : ""; + $this->user->profile->country = (array_key_exists("contact/country/home", $response)) ? $response["contact/country/home"] : ""; + $this->user->profile->zip = (array_key_exists("contact/postalCode/home", $response)) ? $response["contact/postalCode/home"] : ""; + $this->user->profile->gender = (array_key_exists("person/gender", $response)) ? $response["person/gender"] : ""; + $this->user->profile->photoURL = (array_key_exists("media/image/default", $response)) ? $response["media/image/default"] : ""; + + $this->user->profile->birthDay = (array_key_exists("birthDate/birthDay", $response)) ? $response["birthDate/birthDay"] : ""; + $this->user->profile->birthMonth = (array_key_exists("birthDate/birthMonth", $response)) ? $response["birthDate/birthMonth"] : ""; + $this->user->profile->birthYear = (array_key_exists("birthDate/birthDate", $response)) ? $response["birthDate/birthDate"] : ""; + + if (isset($response['namePerson/friendly']) && !empty($response['namePerson/friendly']) && !$this->user->profile->displayName) { + $this->user->profile->displayName = $response["namePerson/friendly"]; + } + + if (isset($response['birthDate']) && !empty($response['birthDate']) && !$this->user->profile->birthDay) { + list( $birthday_year, $birthday_month, $birthday_day ) = $response['birthDate']; + + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + $this->user->profile->birthYear = (int) $birthday_year; + } + + if (!$this->user->profile->displayName) { + $this->user->profile->displayName = trim($this->user->profile->firstName . " " . $this->user->profile->lastName); + } + + if ($this->user->profile->gender == "f") { + $this->user->profile->gender = "female"; + } + + if ($this->user->profile->gender == "m") { + $this->user->profile->gender = "male"; + } + + // set user as logged in + $this->setUserConnected(); + + // with openid providers we get the user profile only once, so store it + Hybrid_Auth::storage()->set("hauth_session.{$this->providerId}.user", $this->user); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + // try to get the user profile from stored data + $this->user = Hybrid_Auth::storage()->get("hauth_session.{$this->providerId}.user"); + + // if not found + if (!is_object($this->user)) { + throw new Exception("User profile request failed! User is not connected to {$this->providerId} or his session has expired.", 6); + } + + return $this->user->profile; + } + +} diff --git a/plugin/social/Hybrid/Providers/Facebook.php b/plugin/social/Hybrid/Providers/Facebook.php new file mode 100644 index 000000000..e373476e0 --- /dev/null +++ b/plugin/social/Hybrid/Providers/Facebook.php @@ -0,0 +1,330 @@ +api->api_base_url = 'https://graph.facebook.com/'; + $this->api->authorize_url = 'https://www.facebook.com/dialog/oauth'; + $this->api->token_url = 'https://graph.facebook.com/v2.3/oauth/access_token'; + $this->api->token_debug = 'https://graph.facebook.com/debug_token'; + + if (!$this->config["keys"]["id"] || !$this->config["keys"]["secret"]) { + throw new Exception("Your application id and secret are required in order to connect to {$this->providerId}.", 4); + } + + } + /** + * {@inheritdoc} + */ + function loginBegin() { + + $token = md5(uniqid(mt_rand(), true)); + Hybrid_Auth::storage()->set('fb_auth_nonce', $token); + + $parameters = array( + "response_type" => "code", + "client_id" => $this->api->client_id, + "redirect_uri" => $this->api->redirect_uri, + "state" => $token, + ); + + Hybrid_Auth::redirect($this->api->authorizeUrl($parameters)); + } + /** + * {@inheritdoc} + */ + function loginFinish() { + + // in case we get error_reason=user_denied&error=access_denied + if (isset($_REQUEST['error']) && $_REQUEST['error'] == "access_denied") { + throw new Exception("Authentication failed! The user denied your request.", 5); + } + + // try to authenicate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + try{ + $response = $this->api->authenticate( $code ); + } + catch( Exception $e ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 ); + } + + // check if authenticated + if ( ! $this->api->authenticated() ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an invalid access token.", 5 ); + } + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + // set user connected locally + $this->setUserConnected(); + + } + + + /** + * {@inheritdoc} + */ + function logout() { + parent::logout(); + } + /** + * {@inheritdoc} + */ + function getUserProfile() { + // request user profile from fb api + try { + $fields = array( + 'id', 'name', 'first_name', 'last_name', 'link', 'website', + 'gender', 'locale', 'about', 'email', 'hometown', 'location', + 'birthday' + ); + $data = $this->api->api('/me?fields=' . implode(',', $fields).'&access_token='.$this->token('access_token')); + } catch (Exception $e) { + $this->logout(); + throw new Exception("User profile request failed! {$this->providerId} returned an error: {$e->getMessage()}", 6, $e); + } + + if( is_object($data) ){ + $data = json_decode(json_encode($data), true); + } + + // if the provider identifier is not received, we assume the auth has failed + if (!isset($data["id"])) { + $this->logout(); + throw new Exception("User profile request failed! {$this->providerId} api returned an invalid response: " . Hybrid_Logger::dumpData( $data ), 6); + } + # store the user profile. + $this->user->profile->identifier = (array_key_exists('id', $data)) ? $data['id'] : ""; + $this->user->profile->username = (array_key_exists('username', $data)) ? $data['username'] : ""; + $this->user->profile->displayName = (array_key_exists('name', $data)) ? $data['name'] : ""; + $this->user->profile->firstName = (array_key_exists('first_name', $data)) ? $data['first_name'] : ""; + $this->user->profile->lastName = (array_key_exists('last_name', $data)) ? $data['last_name'] : ""; + $this->user->profile->photoURL = "https://graph.facebook.com/" . $this->user->profile->identifier . "/picture?width=150&height=150"; + $this->user->profile->coverInfoURL = "https://graph.facebook.com/" . $this->user->profile->identifier . "?fields=cover&access_token=" . $this->token('access_token'); + $this->user->profile->profileURL = (array_key_exists('link', $data)) ? $data['link'] : ""; + $this->user->profile->webSiteURL = (array_key_exists('website', $data)) ? $data['website'] : ""; + $this->user->profile->gender = (array_key_exists('gender', $data)) ? $data['gender'] : ""; + $this->user->profile->language = (array_key_exists('locale', $data)) ? $data['locale'] : ""; + $this->user->profile->description = (array_key_exists('about', $data)) ? $data['about'] : ""; + $this->user->profile->email = (array_key_exists('email', $data)) ? $data['email'] : ""; + $this->user->profile->emailVerified = (array_key_exists('email', $data)) ? $data['email'] : ""; + $this->user->profile->region = (array_key_exists("location", $data) && array_key_exists("name", $data['location'])) ? $data['location']["name"] : ""; + if (!empty($this->user->profile->region)) { + $regionArr = explode(',', $this->user->profile->region); + if (count($regionArr) > 1) { + $this->user->profile->city = trim($regionArr[0]); + $this->user->profile->country = trim($regionArr[1]); + } + } + if (array_key_exists('birthday', $data)) { + list($birthday_month, $birthday_day, $birthday_year) = explode("/", $data['birthday']); + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + $this->user->profile->birthYear = (int) $birthday_year; + } + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + /** + * Attempt to retrieve the url to the cover image given the coverInfoURL + * + * @param string $coverInfoURL coverInfoURL variable + * @return string url to the cover image OR blank string + */ + function getCoverURL($coverInfoURL) { + try { + $headers = get_headers($coverInfoURL); + if (substr($headers[0], 9, 3) != "404") { + $coverOBJ = json_decode(file_get_contents($coverInfoURL)); + if (array_key_exists('cover', $coverOBJ)) { + return $coverOBJ->cover->source; + } + } + } catch (Exception $e) { + } + return ""; + } + /** + * {@inheritdoc} + */ + function getUserContacts() { + $apiCall = '?fields=link,name'; + $returnedContacts = array(); + $pagedList = false; + do { + try { + $response = $this->api->api('/me/friends' . $apiCall); + } catch (Exception $e) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + // Prepare the next call if paging links have been returned + if (array_key_exists('paging', $response) && array_key_exists('next', $response['paging'])) { + $pagedList = true; + $next_page = explode('friends', $response['paging']['next']); + $apiCall = $next_page[1]; + } else { + $pagedList = false; + } + // Add the new page contacts + $returnedContacts = array_merge($returnedContacts, $response['data']); + } while ($pagedList == true); + $contacts = array(); + foreach ($returnedContacts as $item) { + $uc = new Hybrid_User_Contact(); + $uc->identifier = (array_key_exists("id", $item)) ? $item["id"] : ""; + $uc->displayName = (array_key_exists("name", $item)) ? $item["name"] : ""; + $uc->profileURL = (array_key_exists("link", $item)) ? $item["link"] : "https://www.facebook.com/profile.php?id=" . $uc->identifier; + $uc->photoURL = "https://graph.facebook.com/" . $uc->identifier . "/picture?width=150&height=150"; + $contacts[] = $uc; + } + return $contacts; + } + /** + * Update user status + * + * @param mixed $status An array describing the status, or string + * @param string $pageid (optional) User page id + * @return array + * @throw Exception + */ + function setUserStatus($status, $pageid = null) { + if (!is_array($status)) { + $status = array('message' => $status); + } + if (is_null($pageid)) { + $pageid = 'me'; + // if post on page, get access_token page + } else { + $access_token = null; + foreach ($this->getUserPages(true) as $p) { + if (isset($p['id']) && intval($p['id']) == intval($pageid)) { + $access_token = $p['access_token']; + break; + } + } + if (is_null($access_token)) { + throw new Exception("Update user page failed, page not found or not writable!"); + } + $status['access_token'] = $access_token; + } + try { + $response = $this->api->api('/' . $pageid . '/feed', 'post', $status); + } catch (Exception $e) { + throw new Exception("Update user status failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + return $response; + } + /** + * {@inheridoc} + */ + function getUserStatus($postid) { + try { + $postinfo = $this->api->api("/" . $postid); + } catch (Exception $e) { + throw new Exception("Cannot retrieve user status! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + return $postinfo; + } + /** + * {@inheridoc} + */ + function getUserPages($writableonly = false) { + if (( isset($this->config['scope']) && strpos($this->config['scope'], 'manage_pages') === false ) || (!isset($this->config['scope']) && strpos($this->scope, 'manage_pages') === false )) + throw new Exception("User status requires manage_page permission!"); + try { + $pages = $this->api->api("/me/accounts", 'get'); + } catch (Exception $e) { + throw new Exception("Cannot retrieve user pages! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + if (!isset($pages['data'])) { + return array(); + } + if (!$writableonly) { + return $pages['data']; + } + $wrpages = array(); + foreach ($pages['data'] as $p) { + if (isset($p['perms']) && in_array('CREATE_CONTENT', $p['perms'])) { + $wrpages[] = $p; + } + } + return $wrpages; + } + /** + * load the user latest activity + * - timeline : all the stream + * - me : the user activity only + * {@inheritdoc} + */ + function getUserActivity($stream) { + try { + if ($stream == "me") { + $response = $this->api->api('/me/feed'); + } else { + $response = $this->api->api('/me/home'); + } + } catch (Exception $e) { + throw new Exception("User activity stream request failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + if (!$response || !count($response['data'])) { + return array(); + } + $activities = array(); + foreach ($response['data'] as $item) { + if ($stream == "me" && $item["from"]["id"] != $this->api->getUser()) { + continue; + } + $ua = new Hybrid_User_Activity(); + $ua->id = (array_key_exists("id", $item)) ? $item["id"] : ""; + $ua->date = (array_key_exists("created_time", $item)) ? strtotime($item["created_time"]) : ""; + if ($item["type"] == "video") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if ($item["type"] == "link") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if (empty($ua->text) && isset($item["story"])) { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if (empty($ua->text) && isset($item["message"])) { + $ua->text = (array_key_exists("message", $item)) ? $item["message"] : ""; + } + if (!empty($ua->text)) { + $ua->user->identifier = (array_key_exists("id", $item["from"])) ? $item["from"]["id"] : ""; + $ua->user->displayName = (array_key_exists("name", $item["from"])) ? $item["from"]["name"] : ""; + $ua->user->profileURL = "https://www.facebook.com/profile.php?id=" . $ua->user->identifier; + $ua->user->photoURL = "https://graph.facebook.com/" . $ua->user->identifier . "/picture?type=square"; + $activities[] = $ua; + } + } + return $activities; + } +} \ No newline at end of file diff --git a/plugin/social/Hybrid/Providers/Google.php b/plugin/social/Hybrid/Providers/Google.php new file mode 100644 index 000000000..adb2d5a22 --- /dev/null +++ b/plugin/social/Hybrid/Providers/Google.php @@ -0,0 +1,308 @@ + more infos on google APIs: http://developer.google.com (official site) + * or here: http://discovery-check.appspot.com/ (unofficial but up to date) + * default permissions + * {@inheritdoc} + */ + public $scope = "https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/plus.profile.emails.read https://www.google.com/m8/feeds/"; + + /** + * {@inheritdoc} + */ + function initialize() { + parent::initialize(); + + // Provider api end-points + $this->api->authorize_url = "https://accounts.google.com/o/oauth2/auth"; + $this->api->token_url = "https://accounts.google.com/o/oauth2/token"; + $this->api->token_info_url = "https://www.googleapis.com/oauth2/v2/tokeninfo"; + + // Google POST methods require an access_token in the header + $this->api->curl_header = array("Authorization: OAuth " . $this->api->access_token); + + // Override the redirect uri when it's set in the config parameters. This way we prevent + // redirect uri mismatches when authenticating with Google. + if (isset($this->config['redirect_uri']) && !empty($this->config['redirect_uri'])) { + $this->api->redirect_uri = $this->config['redirect_uri']; + } + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + $parameters = array("scope" => $this->scope, "access_type" => "offline"); + $optionals = array("scope", "access_type", "redirect_uri", "approval_prompt", "hd", "state"); + + foreach ($optionals as $parameter) { + if (isset($this->config[$parameter]) && !empty($this->config[$parameter])) { + $parameters[$parameter] = $this->config[$parameter]; + } + if (isset($this->config["scope"]) && !empty($this->config["scope"])) { + $this->scope = $this->config["scope"]; + } + } + + if (isset($this->config['force']) && $this->config['force'] === true) { + $parameters['approval_prompt'] = 'force'; + } + + Hybrid_Auth::redirect($this->api->authorizeUrl($parameters)); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + // refresh tokens if needed + $this->refreshToken(); + + // ask google api for user infos + if (strpos($this->scope, '/auth/plus.profile.emails.read') !== false) { + $verified = $this->api->api("https://www.googleapis.com/plus/v1/people/me"); + + if (!isset($verified->id) || isset($verified->error)) + $verified = new stdClass(); + } else { + $verified = $this->api->api("https://www.googleapis.com/plus/v1/people/me/openIdConnect"); + + if (!isset($verified->sub) || isset($verified->error)) + $verified = new stdClass(); + } + + $response = $this->api->api("https://www.googleapis.com/plus/v1/people/me"); + if (!isset($response->id) || isset($response->error)) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response:" . Hybrid_Logger::dumpData( $response ), 6); + } + + $this->user->profile->identifier = (property_exists($verified, 'id')) ? $verified->id : ((property_exists($response, 'id')) ? $response->id : ""); + $this->user->profile->firstName = (property_exists($response, 'name')) ? $response->name->givenName : ""; + $this->user->profile->lastName = (property_exists($response, 'name')) ? $response->name->familyName : ""; + $this->user->profile->displayName = (property_exists($response, 'displayName')) ? $response->displayName : ""; + $this->user->profile->photoURL = (property_exists($response, 'image')) ? ((property_exists($response->image, 'url')) ? substr($response->image->url, 0, -2) . "200" : '') : ''; + $this->user->profile->profileURL = (property_exists($response, 'url')) ? $response->url : ""; + $this->user->profile->description = (property_exists($response, 'aboutMe')) ? $response->aboutMe : ""; + $this->user->profile->gender = (property_exists($response, 'gender')) ? $response->gender : ""; + $this->user->profile->language = (property_exists($response, 'locale')) ? $response->locale : ((property_exists($verified, 'locale')) ? $verified->locale : ""); + $this->user->profile->email = (property_exists($response, 'email')) ? $response->email : ((property_exists($verified, 'email')) ? $verified->email : ""); + $this->user->profile->emailVerified = (property_exists($verified, 'email')) ? $verified->email : ""; + if (property_exists($response, 'emails')) { + if (count($response->emails) == 1) { + $this->user->profile->email = $response->emails[0]->value; + } else { + foreach ($response->emails as $email) { + if ($email->type == 'account') { + $this->user->profile->email = $email->value; + break; + } + } + } + if (property_exists($verified, 'emails')) { + if (count($verified->emails) == 1) { + $this->user->profile->emailVerified = $verified->emails[0]->value; + } else { + foreach ($verified->emails as $email) { + if ($email->type == 'account') { + $this->user->profile->emailVerified = $email->value; + break; + } + } + } + } + } + $this->user->profile->phone = (property_exists($response, 'phone')) ? $response->phone : ""; + $this->user->profile->country = (property_exists($response, 'country')) ? $response->country : ""; + $this->user->profile->region = (property_exists($response, 'region')) ? $response->region : ""; + $this->user->profile->zip = (property_exists($response, 'zip')) ? $response->zip : ""; + if (property_exists($response, 'placesLived')) { + $this->user->profile->city = ""; + $this->user->profile->address = ""; + foreach ($response->placesLived as $c) { + if (property_exists($c, 'primary')) { + if ($c->primary == true) { + $this->user->profile->address = $c->value; + $this->user->profile->city = $c->value; + break; + } + } else { + if (property_exists($c, 'value')) { + $this->user->profile->address = $c->value; + $this->user->profile->city = $c->value; + } + } + } + } + + // google API returns multiple urls, but a "website" only if it is verified + // see http://support.google.com/plus/answer/1713826?hl=en + if (property_exists($response, 'urls')) { + foreach ($response->urls as $u) { + if (property_exists($u, 'primary') && $u->primary == true) + $this->user->profile->webSiteURL = $u->value; + } + } else { + $this->user->profile->webSiteURL = ''; + } + // google API returns age ranges min and/or max as of https://developers.google.com/+/web/api/rest/latest/people#resource + if (property_exists($response, 'ageRange')) { + if (property_exists($response->ageRange, 'min') && property_exists($response->ageRange, 'max')) { + $this->user->profile->age = $response->ageRange->min . ' - ' . $response->ageRange->max; + } else { + if (property_exists($response->ageRange, 'min')) { + $this->user->profile->age = '>= ' . $response->ageRange->min; + } else { + if (property_exists($response->ageRange, 'max')) { + $this->user->profile->age = '<= ' . $response->ageRange->max; + } else { + $this->user->profile->age = ''; + } + } + } + } else { + $this->user->profile->age = ''; + } + // google API returns birthdays only if a user set 'show in my account' + if (property_exists($response, 'birthday')) { + list($birthday_year, $birthday_month, $birthday_day) = explode('-', $response->birthday); + + $this->user->profile->birthDay = (int) $birthday_day; + $this->user->profile->birthMonth = (int) $birthday_month; + $this->user->profile->birthYear = (int) $birthday_year; + } else { + $this->user->profile->birthDay = 0; + $this->user->profile->birthMonth = 0; + $this->user->profile->birthYear = 0; + } + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + // refresh tokens if needed + $this->refreshToken(); + + $contacts = array(); + if (!isset($this->config['contacts_param'])) { + $this->config['contacts_param'] = array("max-results" => 500); + } + + // Google Gmail and Android contacts + if (strpos($this->scope, '/m8/feeds/') !== false) { + + $response = $this->api->api("https://www.google.com/m8/feeds/contacts/default/full?" + . http_build_query(array_merge(array('alt' => 'json'), $this->config['contacts_param']))); + + if (!$response) { + return array(); + } + + if (isset($response->feed->entry)) { + foreach ($response->feed->entry as $idx => $entry) { + $uc = new Hybrid_User_Contact(); + $uc->email = isset($entry->{'gd$email'}[0]->address) ? (string) $entry->{'gd$email'}[0]->address : ''; + $uc->displayName = isset($entry->title->{'$t'}) ? (string) $entry->title->{'$t'} : ''; + $uc->identifier = ($uc->email != '') ? $uc->email : ''; + $uc->description = ''; + if (property_exists($entry, 'link')) { + /** + * sign links with access_token + */ + if (is_array($entry->link)) { + foreach ($entry->link as $l) { + if (property_exists($l, 'gd$etag') && $l->type == "image/*") { + $uc->photoURL = $this->addUrlParam($l->href, array('access_token' => $this->api->access_token)); + } else if ($l->type == "self") { + $uc->profileURL = $this->addUrlParam($l->href, array('access_token' => $this->api->access_token)); + } + } + } + } else { + $uc->profileURL = ''; + } + if (property_exists($response, 'website')) { + if (is_array($response->website)) { + foreach ($response->website as $w) { + if ($w->primary == true) + $uc->webSiteURL = $w->value; + } + } else { + $uc->webSiteURL = $response->website->value; + } + } else { + $uc->webSiteURL = ''; + } + + $contacts[] = $uc; + } + } + } + + // Google social contacts + if (strpos($this->scope, '/auth/plus.login') !== false) { + + $response = $this->api->api("https://www.googleapis.com/plus/v1/people/me/people/visible?" + . http_build_query($this->config['contacts_param'])); + + if (!$response) { + return array(); + } + + foreach ($response->items as $idx => $item) { + $uc = new Hybrid_User_Contact(); + $uc->email = (property_exists($item, 'email')) ? $item->email : ''; + $uc->displayName = (property_exists($item, 'displayName')) ? $item->displayName : ''; + $uc->identifier = (property_exists($item, 'id')) ? $item->id : ''; + + $uc->description = (property_exists($item, 'objectType')) ? $item->objectType : ''; + $uc->photoURL = (property_exists($item, 'image')) ? ((property_exists($item->image, 'url')) ? $item->image->url : '') : ''; + $uc->profileURL = (property_exists($item, 'url')) ? $item->url : ''; + $uc->webSiteURL = ''; + + $contacts[] = $uc; + } + } + + return $contacts; + } + + /** + * Add query parameters to the $url + * + * @param string $url URL + * @param array $params Parameters to add + * @return string + */ + function addUrlParam($url, array $params){ + $query = parse_url($url, PHP_URL_QUERY); + + // Returns the URL string with new parameters + if ($query) { + $url .= '&' . http_build_query($params); + } else { + $url .= '?' . http_build_query($params); + } + return $url; + } + +} + diff --git a/plugin/social/Hybrid/Providers/Kakao.php b/plugin/social/Hybrid/Providers/Kakao.php new file mode 100644 index 000000000..3038b8352 --- /dev/null +++ b/plugin/social/Hybrid/Providers/Kakao.php @@ -0,0 +1,168 @@ + + * http://facebook.com/chuckoh + * + * Date: 11 10, 2014 + * Time: 01:51 AM + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://www.wtfpl.net/txt/copying/ for more details. + * + */ + +//https://github.com/jinseokoh/additional-providers +class Hybrid_Providers_Kakao extends Hybrid_Provider_Model_OAuth2 +{ + /** + * initialization + */ + function initialize() + { + parent::initialize(); + + // Provider API end-points + $this->api->api_base_url = "https://kapi.kakao.com/v1/"; + $this->api->authorize_url = "https://kauth.kakao.com/oauth/authorize"; + $this->api->token_url = "https://kauth.kakao.com/oauth/token"; + } + + /** + * finish login step + */ + function loginFinish() + { + $error = (array_key_exists('error', $_REQUEST)) ? $_REQUEST['error'] : ""; + // check for errors + if ( $error ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an error: $error", 5 ); + } + // try to authenicate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + try{ + $this->authenticate( $code ); + } + catch( Exception $e ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 ); + } + // check if authenticated + if ( ! $this->api->access_token ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an invalid access token.", 5 ); + } + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + // set user connected locally + $this->setUserConnected(); + } + + /** + * load the user profile + */ + function getUserProfile() + { + $params = array('property_keys'=>'kaccount_email'); + + $this->api->decode_json = false; + $this->api->curl_header = array( 'Authorization: Bearer ' . $this->api->access_token ); + + $data = $this->api->api("user/me", "POST", $params); + + if ( ! isset( $data->id ) ) { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response.", 6); + } + # store the user profile. + $this->user->profile->identifier = @ $data->id; + $this->user->profile->displayName = @ $data->properties->nickname; + $this->user->profile->photoURL = @ $data->properties->thumbnail_image; + $email = @ $data->kaccount_email; + + if( $email ){ + $this->user->profile->email = $email; + } + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + + private function authenticate($code) + { + $params = array( + "grant_type" => "authorization_code", + "client_id" => $this->api->client_id, + "redirect_uri" => $this->api->redirect_uri, + "code" => $code + ); + + $response = $this->request($this->api->token_url, $params, $this->api->curl_authenticate_method); + $response = $this->parseRequestResult($response); + if ( ! $response || ! isset($response->access_token) ) { + throw new Exception("The Authorization Service has return: " . $response->error); + } + if ( isset($response->access_token) ) $this->api->access_token = $response->access_token; + if ( isset($response->refresh_token) ) $this->api->refresh_token = $response->refresh_token; + if ( isset($response->expires_in) ) $this->api->access_token_expires_in = $response->expires_in; + + // calculate when the access token expire + if ( isset($response->expires_in) ) { + $this->api->access_token_expires_at = time() + $response->expires_in; + } + + return $response; + } + + private function request($url, $params=false, $type="GET") + { + if(Class_exists('Hybrid_Logger')){ + Hybrid_Logger::info("Enter OAuth2Client::request( $url )"); + Hybrid_Logger::debug("OAuth2Client::request(). dump request params: ", serialize( $params )); + } + $this->http_info = array(); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL , $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT , $this->api->curl_time_out); + curl_setopt($ch, CURLOPT_USERAGENT , $this->api->curl_useragent); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->api->curl_connect_time_out); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->api->curl_ssl_verifypeer); + curl_setopt($ch, CURLOPT_HTTPHEADER , $this->api->curl_header); + + if ( $this->api->curl_proxy ) { + curl_setopt( $ch, CURLOPT_PROXY, $this->curl_proxy); + } + if ( $type == "POST" ) { + curl_setopt($ch, CURLOPT_POST, 1); + if ($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query($params) ); + } + + $response = curl_exec($ch); + if(Class_exists('Hybrid_Logger')){ + Hybrid_Logger::debug( "OAuth2Client::request(). dump request info: ", serialize(curl_getinfo($ch)) ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request result: ", serialize($response )); + } + $this->http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ch)); + curl_close ($ch); + + return $response; + } + + private function parseRequestResult($result) + { + if ( json_decode($result) ) return json_decode($result); + parse_str( $result, $ouput ); + $result = new StdClass(); + foreach( $ouput as $k => $v ) + $result->$k = $v; + + return $result; + } +} \ No newline at end of file diff --git a/plugin/social/Hybrid/Providers/Naver.php b/plugin/social/Hybrid/Providers/Naver.php new file mode 100644 index 000000000..bd5713be7 --- /dev/null +++ b/plugin/social/Hybrid/Providers/Naver.php @@ -0,0 +1,229 @@ + + * http://facebook.com/chuckoh + * + * Date: 11 11, 2014 + * Time: 11:38 AM + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://www.wtfpl.net/txt/copying/ for more details. + * + */ + +//https://github.com/jinseokoh/additional-providers +class Hybrid_Providers_Naver extends Hybrid_Provider_Model_OAuth2 +{ + /** + * initialization + */ + function initialize() + { + parent::initialize(); + + // Provider API end-points + $this->api->api_base_url = "https://apis.naver.com/nidlogin/"; + $this->api->authorize_url = "https://nid.naver.com/oauth2.0/authorize"; + $this->api->token_url = "https://nid.naver.com/oauth2.0/token"; + } + + /** + * begin login step + */ + function loginBegin() + { + $token = $this->generate_state_token(); + Hybrid_Auth::storage()->set("naver_state_token", $token); + + $parameters = array( + "response_type" => "code", + "client_id" => $this->api->client_id, + "redirect_uri" => $this->api->redirect_uri, + "state" => $token, + ); + + Hybrid_Auth::redirect($this->api->authorizeUrl($parameters)); + } + + /** + * finish login step + */ + function loginFinish() + { + $error = (array_key_exists('error', $_REQUEST)) ? $_REQUEST['error'] : ""; + // check for errors + if ( $error ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an error: $error", 5 ); + } + // try to authenicate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + try{ + $this->authenticate( $code ); + } + catch( Exception $e ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 ); + } + // check if authenticated + if ( ! $this->api->access_token ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an invalid access token.", 5 ); + } + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + // set user connected locally + $this->setUserConnected(); + } + + /** + * set propper headers + */ + function profile($url) { + $this->api->decode_json = false; + $this->api->curl_header = array( 'Authorization: Bearer ' . $this->api->access_token ); + $response = $this->api->get($url, array(), false); + + return $response; + } + + /** + * load the user profile + */ + //https://developers.naver.com/docs/login/profile/ + function getUserProfile() + { + $response = $this->profile("nid/getUserProfile.xml"); + + $xml = @ new SimpleXMLElement($response); + $data = array(); + if ( $xml->result[0]->resultcode == '00' ) { + foreach ($xml->response->children() as $response => $k) { + $data[(string)$response] = (string) $k; + } + } else { + throw new Exception("User profile request failed! {$this->providerId} returned an invalid response.", 6); + } + + # store the user profile. + //$this->user->profile->identifier = (array_key_exists('enc_id',$data))?$data['enc_id']:""; + $this->user->profile->identifier = (array_key_exists('id',$data))?$data['id']:""; + $this->user->profile->age = (array_key_exists('age',$data))?$data['age']:""; + $this->user->profile->displayName = ''; + /* + if( array_key_exists('email',$data) ){ + $tmp = explode("@", $data['email']); + $this->user->profile->displayName = $tmp[0]; + } + */ + $this->user->profile->displayName = (array_key_exists('nickname',$data))?$data['nickname']:""; + $this->user->profile->birthDay = ''; + $this->user->profile->birthMonth = ''; + if( array_key_exists('birthday',$data) ){ + $tmp = explode("-",$data['birthday']); + if( isset($tmp[0]) ){ + $this->user->profile->birthDay = $tmp[0]; + } + if( isset($tmp[1]) ){ + $this->user->profile->birthMonth = $tmp[1]; + } + } + $this->user->profile->email = (array_key_exists('email',$data))?$data['email']:""; + $this->user->profile->emailVerified = (array_key_exists('email',$data))?$data['email']:""; + $this->user->profile->gender = (array_key_exists('gender',$data))?(($data['gender'] == "M")?"male":"female"):""; + $this->user->profile->photoURL = (array_key_exists('profile_image',$data))?$data['profile_image']:""; + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + + private function authenticate($code) + { + $token = Hybrid_Auth::storage()->get("naver_state_token"); + $params = array( + "grant_type" => "authorization_code", + "client_id" => $this->api->client_id, + "client_secret" => $this->api->client_secret, + // "redirect_uri" => $this->api->redirect_uri, + "code" => $code, + "state" => $token + ); + Hybrid_Auth::storage()->set("naver_state_token", null); + + $response = $this->request($this->api->token_url, $params, $this->api->curl_authenticate_method); + $response = $this->parseRequestResult($response); + if ( ! $response || ! isset($response->access_token) ) { + throw new Exception("The Authorization Service has return: " . $response->error); + } + if ( isset($response->access_token) ) $this->api->access_token = $response->access_token; + if ( isset($response->refresh_token) ) $this->api->refresh_token = $response->refresh_token; + if ( isset($response->expires_in) ) $this->api->access_token_expires_in = $response->expires_in; + + // calculate when the access token expire + if ( isset($response->expires_in) ) { + $this->api->access_token_expires_at = time() + $response->expires_in; + } + + return $response; + } + + private function request($url, $params=false, $type="GET") + { + if(Class_exists('Hybrid_Logger')){ + Hybrid_Logger::info("Enter OAuth2Client::request( $url )"); + Hybrid_Logger::debug("OAuth2Client::request(). dump request params: ", serialize( $params )); + } + $this->http_info = array(); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL , $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT , $this->api->curl_time_out); + curl_setopt($ch, CURLOPT_USERAGENT , $this->api->curl_useragent); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->api->curl_connect_time_out); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->api->curl_ssl_verifypeer); + curl_setopt($ch, CURLOPT_HTTPHEADER , $this->api->curl_header); + + if ( $this->api->curl_proxy ) { + curl_setopt( $ch, CURLOPT_PROXY, $this->curl_proxy); + } + if ( $type == "POST" ) { + curl_setopt($ch, CURLOPT_POST, 1); + if ($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query($params) ); + } + + $response = curl_exec($ch); + if(Class_exists('Hybrid_Logger')){ + Hybrid_Logger::debug( "OAuth2Client::request(). dump request info: ", serialize(curl_getinfo($ch)) ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request result: ", serialize($response )); + } + $this->http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ch)); + curl_close ($ch); + + return $response; + } + + private function parseRequestResult($result) + { + if ( json_decode($result) ) return json_decode($result); + parse_str( $result, $ouput ); + $result = new StdClass(); + foreach( $ouput as $k => $v ) + $result->$k = $v; + + return $result; + } + + private function generate_state_token() { + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); + } +} diff --git a/plugin/social/Hybrid/Providers/Payco.php b/plugin/social/Hybrid/Providers/Payco.php new file mode 100644 index 000000000..5eb5694f4 --- /dev/null +++ b/plugin/social/Hybrid/Providers/Payco.php @@ -0,0 +1,382 @@ +api->api_base_url = 'https://id.payco.com/oauth2.0/'; + $this->api->authorize_url = 'https://id.payco.com/oauth2.0/authorize'; + $this->api->token_url = 'https://id.payco.com/oauth2.0/token'; + $this->api->token_info = 'https://apis3.krp.toastoven.net/payco/friends/getIdNoByFriendsToken.json'; + $this->api->profile_url = 'https://apis3.krp.toastoven.net/payco/friends/getMemberProfileByFriendsToken.json'; + + if (!$this->config["keys"]["id"] || !$this->config["keys"]["secret"]) { + throw new Exception("Your application id and secret are required in order to connect to {$this->providerId}.", 4); + } + + } + /** + * {@inheritdoc} + */ + function loginBegin() { + + $token = md5(uniqid(mt_rand(), true)); + Hybrid_Auth::storage()->set('payco_auth_token', $token); + + $parameters = array( + "response_type" => "code", + "client_id" => $this->api->client_id, + "redirect_uri" => $this->api->redirect_uri, + "state" => $token, + "userLocale" => "ko_KR", + "serviceProviderCode" => "FRIENDS", + ); + + Hybrid_Auth::redirect($this->api->authorizeUrl($parameters)); + + exit; + + } + /** + * {@inheritdoc} + */ + function loginFinish() { + + // in case we get error_reason=user_denied&error=access_denied + if (isset($_REQUEST['error']) && $_REQUEST['error'] == "access_denied") { + throw new Exception("Authentication failed! The user denied your request.", 5); + } + + // try to authenicate user + $code = (array_key_exists('code', $_REQUEST)) ? $_REQUEST['code'] : ""; + try{ + $response = $this->api->authenticate( $code ); + } + catch( Exception $e ){ + throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 ); + } + + // check if authenticated + if ( ! $this->api->authenticated() ){ + throw new Exception( "Authentication failed! {$this->providerId} returned an invalid access token.", 5 ); + } + // store tokens + $this->token("access_token", $this->api->access_token); + $this->token("refresh_token", $this->api->refresh_token); + $this->token("expires_in", $this->api->access_token_expires_in); + $this->token("expires_at", $this->api->access_token_expires_at); + + $this->setUserConnected(); + + } + + function check_valid_access_token(){ + + $params = array( + 'body' => array( + 'client_id'=>$this->api->client_id, + 'access_token'=>$this->api->access_token, + ), + ); + + $this->api->curl_header = array( + + 'Content-Type:application/json', + 'client_id: '.$this->api->client_id, + 'access_token: '.$this->api->access_token, + + ); + + $response = $this->api->api( $this->api->token_info, 'POST', $params ); + + if( is_object($response) && !empty($response->idNo) && $response->header->successful ){ + $this->idNo = $response->idNo; + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + function logout() { + parent::logout(); + } + /** + * {@inheritdoc} + */ + + /** + * set propper headers + */ + function getUserProfile() { + + $data = null; + + // request user profile + try { + + if( $this->check_valid_access_token() ){ + $params = array( + 'body' => array( + 'client_id'=>$this->api->client_id, + 'access_token'=>$this->api->access_token, + 'MemberProfile'=>'idNo,id,name', + 'idNo'=>$this->idNo, + ), + ); + + $this->api->curl_header = array( + 'Content-Type:application/json', + 'client_id: '.$this->api->client_id, + 'access_token: '.$this->api->access_token, + 'Authorization: Bearer ' . $this->api->access_token, + ); + + $response = $this->api->api( $this->api->profile_url, 'POST', $params ); + } + + } catch (Exception $e) { + throw new Exception("User profile request failed! {$this->providerId} returned an error: {$e->getMessage()}", 6, $e); + } + + if( ! is_object($response) || property_exists($response, 'error_code') ){ + $this->logout(); + + throw new Exception( "Authentication failed! {$this->providerId} returned an invalid access token.", 5 ); + } + + if( is_object($response) ){ + $result = json_decode(json_encode($response), true); + $data = $result['memberProfile']; + } + + // if the provider identifier is not received, we assume the auth has failed + if (!isset($data["id"])) { + $this->logout(); + throw new Exception("User profile request failed! {$this->providerId} api returned an invalid response: " . Hybrid_Logger::dumpData( $data ), 6); + } + + # store the user profile. + $this->user->profile->identifier = (array_key_exists('idNo', $data)) ? $data['idNo'] : ""; + $this->user->profile->username = (array_key_exists('name', $data)) ? $data['name'] : ""; + $this->user->profile->displayName = (array_key_exists('name', $data)) ? $data['name'] : ""; + $this->user->profile->age = (array_key_exists('ageGroup', $data)) ? $data['ageGroup'] : ""; + + include_once(G5_LIB_PATH.'/register.lib.php'); + + $payco_no = substr(base_convert($this->user->profile->identifier, 16, 36), 0, 16); + $email = (array_key_exists('id', $data)) ? $data['id'] : ""; + + $this->user->profile->gender = (array_key_exists('sexCode', $data)) ? $data['sexCode'] : ""; + + $this->user->profile->email = ! valid_mb_email($email) ? $email : ""; + $this->user->profile->emailVerified = ! valid_mb_email($email) ? $email : ""; + + + if (array_key_exists('birthdayMMdd', $data)) { + $this->user->profile->birthMonth = substr($data['birthdayMMdd'], 0, 2); + $this->user->profile->birthDay = substr($data['birthdayMMdd'], 2, 4); + } + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + /** + * Attempt to retrieve the url to the cover image given the coverInfoURL + * + * @param string $coverInfoURL coverInfoURL variable + * @return string url to the cover image OR blank string + */ + function getCoverURL($coverInfoURL) { + try { + $headers = get_headers($coverInfoURL); + if (substr($headers[0], 9, 3) != "404") { + $coverOBJ = json_decode(file_get_contents($coverInfoURL)); + if (array_key_exists('cover', $coverOBJ)) { + return $coverOBJ->cover->source; + } + } + } catch (Exception $e) { + } + return ""; + } + /** + * {@inheritdoc} + */ + function getUserContacts() { + $apiCall = '?fields=link,name'; + $returnedContacts = array(); + $pagedList = false; + do { + try { + $response = $this->api->api('/me/friends' . $apiCall); + } catch (Exception $e) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + // Prepare the next call if paging links have been returned + if (array_key_exists('paging', $response) && array_key_exists('next', $response['paging'])) { + $pagedList = true; + $next_page = explode('friends', $response['paging']['next']); + $apiCall = $next_page[1]; + } else { + $pagedList = false; + } + // Add the new page contacts + $returnedContacts = array_merge($returnedContacts, $response['data']); + } while ($pagedList == true); + $contacts = array(); + foreach ($returnedContacts as $item) { + $uc = new Hybrid_User_Contact(); + $uc->identifier = (array_key_exists("id", $item)) ? $item["id"] : ""; + $uc->displayName = (array_key_exists("name", $item)) ? $item["name"] : ""; + $uc->profileURL = (array_key_exists("link", $item)) ? $item["link"] : "https://www.facebook.com/profile.php?id=" . $uc->identifier; + $uc->photoURL = "https://graph.facebook.com/" . $uc->identifier . "/picture?width=150&height=150"; + $contacts[] = $uc; + } + return $contacts; + } + /** + * Update user status + * + * @param mixed $status An array describing the status, or string + * @param string $pageid (optional) User page id + * @return array + * @throw Exception + */ + function setUserStatus($status, $pageid = null) { + if (!is_array($status)) { + $status = array('message' => $status); + } + if (is_null($pageid)) { + $pageid = 'me'; + // if post on page, get access_token page + } else { + $access_token = null; + foreach ($this->getUserPages(true) as $p) { + if (isset($p['id']) && intval($p['id']) == intval($pageid)) { + $access_token = $p['access_token']; + break; + } + } + if (is_null($access_token)) { + throw new Exception("Update user page failed, page not found or not writable!"); + } + $status['access_token'] = $access_token; + } + try { + $response = $this->api->api('/' . $pageid . '/feed', 'post', $status); + } catch (Exception $e) { + throw new Exception("Update user status failed! {$this->providerId} returned an error {$e->getMessage()}", 0, $e); + } + return $response; + } + /** + * {@inheridoc} + */ + function getUserStatus($postid) { + try { + $postinfo = $this->api->api("/" . $postid); + } catch (Exception $e) { + throw new Exception("Cannot retrieve user status! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + return $postinfo; + } + /** + * {@inheridoc} + */ + function getUserPages($writableonly = false) { + if (( isset($this->config['scope']) && strpos($this->config['scope'], 'manage_pages') === false ) || (!isset($this->config['scope']) && strpos($this->scope, 'manage_pages') === false )) + throw new Exception("User status requires manage_page permission!"); + try { + $pages = $this->api->api("/me/accounts", 'get'); + } catch (Exception $e) { + throw new Exception("Cannot retrieve user pages! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + if (!isset($pages['data'])) { + return array(); + } + if (!$writableonly) { + return $pages['data']; + } + $wrpages = array(); + foreach ($pages['data'] as $p) { + if (isset($p['perms']) && in_array('CREATE_CONTENT', $p['perms'])) { + $wrpages[] = $p; + } + } + return $wrpages; + } + /** + * load the user latest activity + * - timeline : all the stream + * - me : the user activity only + * {@inheritdoc} + */ + function getUserActivity($stream) { + try { + if ($stream == "me") { + $response = $this->api->api('/me/feed'); + } else { + $response = $this->api->api('/me/home'); + } + } catch (Exception $e) { + throw new Exception("User activity stream request failed! {$this->providerId} returned an error: {$e->getMessage()}", 0, $e); + } + if (!$response || !count($response['data'])) { + return array(); + } + $activities = array(); + foreach ($response['data'] as $item) { + if ($stream == "me" && $item["from"]["id"] != $this->api->getUser()) { + continue; + } + $ua = new Hybrid_User_Activity(); + $ua->id = (array_key_exists("id", $item)) ? $item["id"] : ""; + $ua->date = (array_key_exists("created_time", $item)) ? strtotime($item["created_time"]) : ""; + if ($item["type"] == "video") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if ($item["type"] == "link") { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if (empty($ua->text) && isset($item["story"])) { + $ua->text = (array_key_exists("link", $item)) ? $item["link"] : ""; + } + if (empty($ua->text) && isset($item["message"])) { + $ua->text = (array_key_exists("message", $item)) ? $item["message"] : ""; + } + if (!empty($ua->text)) { + $ua->user->identifier = (array_key_exists("id", $item["from"])) ? $item["from"]["id"] : ""; + $ua->user->displayName = (array_key_exists("name", $item["from"])) ? $item["from"]["name"] : ""; + $ua->user->profileURL = "https://www.facebook.com/profile.php?id=" . $ua->user->identifier; + $ua->user->photoURL = "https://graph.facebook.com/" . $ua->user->identifier . "/picture?type=square"; + $activities[] = $ua; + } + } + return $activities; + } +} \ No newline at end of file diff --git a/plugin/social/Hybrid/Providers/Twitter.php b/plugin/social/Hybrid/Providers/Twitter.php new file mode 100644 index 000000000..cb1c1706b --- /dev/null +++ b/plugin/social/Hybrid/Providers/Twitter.php @@ -0,0 +1,266 @@ +api->api_base_url = "https://api.twitter.com/1.1/"; + $this->api->authorize_url = "https://api.twitter.com/oauth/authenticate"; + $this->api->request_token_url = "https://api.twitter.com/oauth/request_token"; + $this->api->access_token_url = "https://api.twitter.com/oauth/access_token"; + + if (isset($this->config['api_version']) && $this->config['api_version']) { + $this->api->api_base_url = "https://api.twitter.com/{$this->config['api_version']}/"; + } + + if (isset($this->config['authorize']) && $this->config['authorize']) { + $this->api->authorize_url = "https://api.twitter.com/oauth/authorize"; + } + + $this->api->curl_auth_header = false; + } + + /** + * {@inheritdoc} + */ + function loginBegin() { + // Initiate the Reverse Auth flow; cf. https://dev.twitter.com/docs/ios/using-reverse-auth + if (isset($_REQUEST['reverse_auth']) && ($_REQUEST['reverse_auth'] == 'yes')) { + $stage1 = $this->api->signedRequest($this->api->request_token_url, 'POST', array('x_auth_mode' => 'reverse_auth')); + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + $responseObj = array('x_reverse_auth_parameters' => $stage1, 'x_reverse_auth_target' => $this->config["keys"]["key"]); + $response = json_encode($responseObj); + header("Content-Type: application/json", true, 200); + echo $response; + die(); + } + $tokens = $this->api->requestToken($this->endpoint); + + // request tokens as received from provider + $this->request_tokens_raw = $tokens; + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Authentication failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 5); + } + + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid oauth token.", 5); + } + + $this->token("request_token", $tokens["oauth_token"]); + $this->token("request_token_secret", $tokens["oauth_token_secret"]); + + // redirect the user to the provider authentication url with force_login + if (( isset($this->config['force_login']) && $this->config['force_login'] ) || ( isset($this->config['force']) && $this->config['force'] === true )) { + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens, array('force_login' => true))); + } + + // else, redirect the user to the provider authentication url + Hybrid_Auth::redirect($this->api->authorizeUrl($tokens)); + } + + /** + * {@inheritdoc} + */ + function loginFinish() { + // in case we are completing a Reverse Auth flow; cf. https://dev.twitter.com/docs/ios/using-reverse-auth + if (isset($_REQUEST['oauth_token_secret'])) { + $tokens = $_REQUEST; + $this->access_tokens_raw = $tokens; + + // we should have an access_token unless something has gone wrong + if (!isset($tokens["oauth_token"])) { + throw new Exception("Authentication failed! {$this->providerId} returned an invalid access token.", 5); + } + + // Get rid of tokens we don't need + $this->deleteToken("request_token"); + $this->deleteToken("request_token_secret"); + + // Store access_token and secret for later use + $this->token("access_token", $tokens['oauth_token']); + $this->token("access_token_secret", $tokens['oauth_token_secret']); + + // set user as logged in to the current provider + $this->setUserConnected(); + return; + } + parent::loginFinish(); + } + + /** + * {@inheritdoc} + */ + function getUserProfile() { + $includeEmail = isset($this->config['includeEmail']) ? (bool) $this->config['includeEmail'] : false; + $response = $this->api->get('account/verify_credentials.json'. ($includeEmail ? '?include_email=true' : '')); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User profile request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code), 6); + } + + if (!is_object($response) || !isset($response->id)) { + throw new Exception("User profile request failed! {$this->providerId} api returned an invalid response: " . Hybrid_Logger::dumpData( $response ), 6); + } + + # store the user profile. + $this->user->profile->identifier = (property_exists($response, 'id')) ? $response->id : ""; + $this->user->profile->displayName = (property_exists($response, 'screen_name')) ? $response->screen_name : ""; + $this->user->profile->description = (property_exists($response, 'description')) ? $response->description : ""; + $this->user->profile->firstName = (property_exists($response, 'name')) ? $response->name : ""; + $this->user->profile->photoURL = (property_exists($response, 'profile_image_url')) ? (str_replace('_normal', '', $response->profile_image_url)) : ""; + $this->user->profile->profileURL = (property_exists($response, 'screen_name')) ? ("http://twitter.com/" . $response->screen_name) : ""; + $this->user->profile->webSiteURL = (property_exists($response, 'url')) ? $response->url : ""; + $this->user->profile->region = (property_exists($response, 'location')) ? $response->location : ""; + if($includeEmail) $this->user->profile->email = (property_exists($response, 'email')) ? $response->email : ""; + if($includeEmail) $this->user->profile->emailVerified = (property_exists($response, 'email')) ? $response->email : ""; + + $this->user->profile->sid = get_social_convert_id( $this->user->profile->identifier, $this->providerId ); + + return $this->user->profile; + } + + /** + * {@inheritdoc} + */ + function getUserContacts() { + $parameters = array('cursor' => '-1'); + $response = $this->api->get('friends/ids.json', $parameters); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if (!$response || !count($response->ids)) { + return array(); + } + + // 75 id per time should be okey + $contactsids = array_chunk($response->ids, 75); + + $contacts = array(); + + foreach ($contactsids as $chunk) { + $parameters = array('user_id' => implode(",", $chunk)); + $response = $this->api->get('users/lookup.json', $parameters); + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User contacts request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if ($response && count($response)) { + foreach ($response as $item) { + $uc = new Hybrid_User_Contact(); + + $uc->identifier = (property_exists($item, 'id')) ? $item->id : ""; + $uc->displayName = (property_exists($item, 'name')) ? $item->name : ""; + $uc->profileURL = (property_exists($item, 'screen_name')) ? ("http://twitter.com/" . $item->screen_name) : ""; + $uc->photoURL = (property_exists($item, 'profile_image_url')) ? $item->profile_image_url : ""; + $uc->description = (property_exists($item, 'description')) ? $item->description : ""; + + $contacts[] = $uc; + } + } + } + + return $contacts; + } + + /** + * {@inheritdoc} + */ + function setUserStatus($status) { + + if (is_array($status) && isset($status['message']) && isset($status['picture'])) { + $response = $this->api->post('statuses/update_with_media.json', array('status' => $status['message'], 'media[]' => file_get_contents($status['picture'])), null, null, true); + } else { + $response = $this->api->post('statuses/update.json', array('status' => $status)); + } + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("Update user status failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + function getUserStatus($tweetid) { + $info = $this->api->get('statuses/show.json?id=' . $tweetid . '&include_entities=true'); + + // check the last HTTP status code returned + if ($this->api->http_code != 200 || !isset($info->id)) { + throw new Exception("Cannot retrieve user status! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + return $info; + } + + /** + * load the user latest activity + * - timeline : all the stream + * - me : the user activity only + * + * by default return the timeline + * {@inheritdoc} + */ + function getUserActivity($stream) { + if ($stream == "me") { + $response = $this->api->get('statuses/user_timeline.json'); + } else { + $response = $this->api->get('statuses/home_timeline.json'); + } + + // check the last HTTP status code returned + if ($this->api->http_code != 200) { + throw new Exception("User activity stream request failed! {$this->providerId} returned an error. " . $this->errorMessageByStatus($this->api->http_code)); + } + + if (!$response) { + return array(); + } + + $activities = array(); + + foreach ($response as $item) { + $ua = new Hybrid_User_Activity(); + + $ua->id = (property_exists($item, 'id')) ? $item->id : ""; + $ua->date = (property_exists($item, 'created_at')) ? strtotime($item->created_at) : ""; + $ua->text = (property_exists($item, 'text')) ? $item->text : ""; + + $ua->user->identifier = (property_exists($item->user, 'id')) ? $item->user->id : ""; + $ua->user->displayName = (property_exists($item->user, 'name')) ? $item->user->name : ""; + $ua->user->profileURL = (property_exists($item->user, 'screen_name')) ? ("http://twitter.com/" . $item->user->screen_name) : ""; + $ua->user->photoURL = (property_exists($item->user, 'profile_image_url')) ? $item->user->profile_image_url : ""; + + $activities[] = $ua; + } + + return $activities; + } + +} diff --git a/plugin/social/Hybrid/Storage.php b/plugin/social/Hybrid/Storage.php new file mode 100644 index 000000000..16a2f6cb2 --- /dev/null +++ b/plugin/social/Hybrid/Storage.php @@ -0,0 +1,141 @@ +config("php_session_id", session_id()); + $this->config("version", Hybrid_Auth::$version); + } + + /** + * Saves a value in the config storage, or returns config if value is null + * + * @param string $key Config name + * @param string $value Config value + * @return array|null + */ + public function config($key, $value = null) { + $key = strtolower($key); + + if ($value) { + $_SESSION["HA::CONFIG"][$key] = serialize($value); + } elseif (isset($_SESSION["HA::CONFIG"][$key])) { + return unserialize($_SESSION["HA::CONFIG"][$key]); + } + + return null; + } + + /** + * Returns value from session storage + * + * @param string $key Key + * @return string|null + */ + public function get($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"], $_SESSION["HA::STORE"][$key])) { + return unserialize($_SESSION["HA::STORE"][$key]); + } + + return null; + } + + /** + * Saves a key value pair to the session storage + * + * @param string $key Key + * @param string $value Value + * @return void + */ + public function set($key, $value) { + $key = strtolower($key); + $_SESSION["HA::STORE"][$key] = serialize($value); + } + + /** + * Clear session storage + * @return void + */ + function clear() { + $_SESSION["HA::STORE"] = array(); + } + + /** + * Delete a specific key from session storage + * + * @param string $key Key + * @return void + */ + function delete($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"], $_SESSION["HA::STORE"][$key])) { + $f = $_SESSION['HA::STORE']; + unset($f[$key]); + $_SESSION["HA::STORE"] = $f; + } + } + + /** + * Delete all keys recursively from session storage + * + * @param string $key Key + * @retun void + */ + function deleteMatch($key) { + $key = strtolower($key); + + if (isset($_SESSION["HA::STORE"]) && count($_SESSION["HA::STORE"])) { + $f = $_SESSION['HA::STORE']; + foreach ($f as $k => $v) { + if (strstr($k, $key)) { + unset($f[$k]); + } + } + $_SESSION["HA::STORE"] = $f; + } + } + + /** + * Returns session storage as a serialized string + * @return string|null + */ + function getSessionData() { + if (isset($_SESSION["HA::STORE"])) { + return serialize($_SESSION["HA::STORE"]); + } + return null; + } + + /** + * Restores the session from serialized session data + * + * @param string $sessiondata Serialized session data + * @return void + */ + function restoreSessionData($sessiondata = null) { + $_SESSION["HA::STORE"] = unserialize($sessiondata); + } + +} diff --git a/plugin/social/Hybrid/StorageInterface.php b/plugin/social/Hybrid/StorageInterface.php new file mode 100644 index 000000000..5b171ec3a --- /dev/null +++ b/plugin/social/Hybrid/StorageInterface.php @@ -0,0 +1,29 @@ +timestamp = time(); + $this->profile = new Hybrid_User_Profile(); + } + +} diff --git a/plugin/social/Hybrid/User_Activity.php b/plugin/social/Hybrid/User_Activity.php new file mode 100644 index 000000000..5d0712ab6 --- /dev/null +++ b/plugin/social/Hybrid/User_Activity.php @@ -0,0 +1,55 @@ +user = new stdClass(); + + // typically, we should have a few information about the user who created the event from social apis + $this->user->identifier = null; + $this->user->displayName = null; + $this->user->profileURL = null; + $this->user->photoURL = null; + } + +} diff --git a/plugin/social/Hybrid/User_Contact.php b/plugin/social/Hybrid/User_Contact.php new file mode 100644 index 000000000..976595835 --- /dev/null +++ b/plugin/social/Hybrid/User_Contact.php @@ -0,0 +1,60 @@ + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + \ No newline at end of file diff --git a/plugin/social/Hybrid/resources/index.html b/plugin/social/Hybrid/resources/index.html new file mode 100644 index 000000000..c942a79ce --- /dev/null +++ b/plugin/social/Hybrid/resources/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + \ No newline at end of file diff --git a/plugin/social/Hybrid/resources/openid_policy.html b/plugin/social/Hybrid/resources/openid_policy.html new file mode 100644 index 000000000..01462daec --- /dev/null +++ b/plugin/social/Hybrid/resources/openid_policy.html @@ -0,0 +1,10 @@ + + + OpenID Policy + + + + + \ No newline at end of file diff --git a/plugin/social/Hybrid/resources/openid_realm.html b/plugin/social/Hybrid/resources/openid_realm.html new file mode 100644 index 000000000..78b1b1d62 --- /dev/null +++ b/plugin/social/Hybrid/resources/openid_realm.html @@ -0,0 +1,13 @@ + + + HybridAuth Endpoint + + + + +

    HybridAuth

    + Open Source Social Sign On PHP Library. +
    + hybridauth.sourceforge.net/ + + diff --git a/plugin/social/Hybrid/resources/openid_xrds.xml b/plugin/social/Hybrid/resources/openid_xrds.xml new file mode 100644 index 000000000..ab94e5c01 --- /dev/null +++ b/plugin/social/Hybrid/resources/openid_xrds.xml @@ -0,0 +1,12 @@ + + + + + http://specs.openid.net/auth/2.0/return_to + {RETURN_TO_URL} + + + \ No newline at end of file diff --git a/plugin/social/Hybrid/thirdparty/OAuth/OAuth.php b/plugin/social/Hybrid/thirdparty/OAuth/OAuth.php new file mode 100644 index 000000000..876665fae --- /dev/null +++ b/plugin/social/Hybrid/thirdparty/OAuth/OAuth.php @@ -0,0 +1,901 @@ +key = $key; + $this->secret = $secret; + $this->callback_url = $callback_url; + } + + function __toString() { + return "OAuthConsumer[key=$this->key,secret=$this->secret]"; + } +} + +class OAuthToken { + // access tokens and request tokens + public $key; + public $secret; + + /** + * key = the token + * secret = the token secret + */ + function __construct($key, $secret) { + $this->key = $key; + $this->secret = $secret; + } + + /** + * generates the basic string serialization of a token that a server + * would respond to request_token and access_token calls with + */ + function to_string() { + return "oauth_token=" . + OAuthUtil::urlencode_rfc3986($this->key) . + "&oauth_token_secret=" . + OAuthUtil::urlencode_rfc3986($this->secret); + } + + function __toString() { + return $this->to_string(); + } +} + +/** + * A class for implementing a Signature Method + * See section 9 ("Signing Requests") in the spec + */ +abstract class OAuthSignatureMethod { + /** + * Needs to return the name of the Signature Method (ie HMAC-SHA1) + * @return string + */ + abstract public function get_name(); + + /** + * Build up the signature + * NOTE: The output of this function MUST NOT be urlencoded. + * the encoding is handled in OAuthRequest when the final + * request is serialized + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @return string + */ + abstract public function build_signature($request, $consumer, $token); + + /** + * Verifies that a given signature is correct + * @param OAuthRequest $request + * @param OAuthConsumer $consumer + * @param OAuthToken $token + * @param string $signature + * @return bool + */ + public function check_signature($request, $consumer, $token, $signature) { + $built = $this->build_signature($request, $consumer, $token); + + // Check for zero length, although unlikely here + if (strlen($built) == 0 || strlen($signature) == 0) { + return false; + } + + if (strlen($built) != strlen($signature)) { + return false; + } + + // Avoid a timing leak with a (hopefully) time insensitive compare + $result = 0; + for ($i = 0; $i < strlen($signature); $i++) { + $result |= ord($built{$i}) ^ ord($signature{$i}); + } + + return $result == 0; + } +} + +/** + * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] + * where the Signature Base String is the text and the key is the concatenated values (each first + * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' + * character (ASCII code 38) even if empty. + * - Chapter 9.2 ("HMAC-SHA1") + */ +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { + function get_name() { + return "HMAC-SHA1"; + } + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + + return base64_encode(hash_hmac('sha1', $base_string, $key, true)); + } +} + +/** + * The PLAINTEXT method does not provide any security protection and SHOULD only be used + * over a secure channel such as HTTPS. It does not use the Signature Base String. + * - Chapter 9.4 ("PLAINTEXT") + */ +class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { + public function get_name() { + return "PLAINTEXT"; + } + + /** + * oauth_signature is set to the concatenated encoded values of the Consumer Secret and + * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is + * empty. The result MUST be encoded again. + * - Chapter 9.4.1 ("Generating Signatures") + * + * Please note that the second encoding MUST NOT happen in the SignatureMethod, as + * OAuthRequest handles this! + */ + public function build_signature($request, $consumer, $token) { + $key_parts = array( + $consumer->secret, + ($token) ? $token->secret : "" + ); + + $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); + $key = implode('&', $key_parts); + $request->base_string = $key; + + return $key; + } +} + +/** + * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in + * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for + * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a + * verified way to the Service Provider, in a manner which is beyond the scope of this + * specification. + * - Chapter 9.3 ("RSA-SHA1") + */ +abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { + public function get_name() { + return "RSA-SHA1"; + } + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // (2) fetch via http using a url provided by the requester + // (3) some sort of specific discovery code based on request + // + // Either way should return a string representation of the certificate + protected abstract function fetch_public_cert(&$request); + + // Up to the SP to implement this lookup of keys. Possible ideas are: + // (1) do a lookup in a table of trusted certs keyed off of consumer + // + // Either way should return a string representation of the certificate + protected abstract function fetch_private_cert(&$request); + + public function build_signature($request, $consumer, $token) { + $base_string = $request->get_signature_base_string(); + $request->base_string = $base_string; + + // Fetch the private key cert based on the request + $cert = $this->fetch_private_cert($request); + + // Pull the private key ID from the certificate + $privatekeyid = openssl_get_privatekey($cert); + + // Sign using the key + $ok = openssl_sign($base_string, $signature, $privatekeyid); + + // Release the key resource + openssl_free_key($privatekeyid); + + return base64_encode($signature); + } + + public function check_signature($request, $consumer, $token, $signature) { + $decoded_sig = base64_decode($signature); + + $base_string = $request->get_signature_base_string(); + + // Fetch the public key cert based on the request + $cert = $this->fetch_public_cert($request); + + // Pull the public key ID from the certificate + $publickeyid = openssl_get_publickey($cert); + + // Check the computed signature against the one passed in the query + $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); + + // Release the key resource + openssl_free_key($publickeyid); + + return $ok == 1; + } +} + +class OAuthRequest { + protected $parameters; + protected $http_method; + protected $http_url; + // for debug purposes + public $base_string; + public static $version = '1.0'; + public static $POST_INPUT = 'php://input'; + + function __construct($http_method, $http_url, $parameters=null) { + $parameters = ($parameters) ? $parameters : array(); + $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; + } + + + /** + * attempt to build up a request from what was passed to the server + */ + public static function from_request($http_method=null, $http_url=null, $parameters=null) { + $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") + ? 'http' + : 'https'; + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO']; + } + $http_url = ($http_url) ? $http_url : $scheme . + '://' . $_SERVER['SERVER_NAME'] . + ':' . + $_SERVER['SERVER_PORT'] . + $_SERVER['REQUEST_URI']; + $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; + + // We weren't handed any parameters, so let's find the ones relevant to + // this request. + // If you run XML-RPC or similar you should use this to provide your own + // parsed parameter-list + if (!$parameters) { + // Find request headers + $request_headers = OAuthUtil::get_headers(); + + // Parse the query-string to find GET parameters + $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); + + // It's a POST request of the proper content-type, so parse POST + // parameters and add those overriding any duplicates from GET + if ($http_method == "POST" + && isset($request_headers['Content-Type']) + && strstr($request_headers['Content-Type'], + 'application/x-www-form-urlencoded') + ) { + $post_data = OAuthUtil::parse_parameters( + file_get_contents(self::$POST_INPUT) + ); + $parameters = array_merge($parameters, $post_data); + } + + // We have a Authorization-header with OAuth data. Parse the header + // and add those overriding any duplicates from GET or POST + if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { + $header_parameters = OAuthUtil::split_header( + $request_headers['Authorization'] + ); + $parameters = array_merge($parameters, $header_parameters); + } + + } + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + /** + * pretty much a helper function to set up the request + */ + public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=null) { + $parameters = ($parameters) ? $parameters : array(); + $defaults = array("oauth_version" => OAuthRequest::$version, + "oauth_nonce" => OAuthRequest::generate_nonce(), + "oauth_timestamp" => OAuthRequest::generate_timestamp(), + "oauth_consumer_key" => $consumer->key); + if ($token) + $defaults['oauth_token'] = $token->key; + + $parameters = array_merge($defaults, $parameters); + + return new OAuthRequest($http_method, $http_url, $parameters); + } + + public function set_parameter($name, $value, $allow_duplicates = true) { + if ($allow_duplicates && isset($this->parameters[$name])) { + // We have already added parameter(s) with this name, so add to the list + if (is_scalar($this->parameters[$name])) { + // This is the first duplicate, so transform scalar (string) + // into an array so we can add the duplicates + $this->parameters[$name] = array($this->parameters[$name]); + } + + $this->parameters[$name][] = $value; + } else { + $this->parameters[$name] = $value; + } + } + + public function get_parameter($name) { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + public function get_parameters() { + return $this->parameters; + } + + public function unset_parameter($name) { + unset($this->parameters[$name]); + } + + /** + * The request parameters, sorted and concatenated into a normalized string. + * @return string + */ + public function get_signable_parameters() { + // Grab all parameters + $params = $this->parameters; + + // Remove oauth_signature if present + // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") + if (isset($params['oauth_signature'])) { + unset($params['oauth_signature']); + } + + return OAuthUtil::build_http_query($params); + } + + /** + * Returns the base string of this request + * + * The base string defined as the method, the url + * and the parameters (normalized), each urlencoded + * and the concated with &. + */ + public function get_signature_base_string() { + $parts = array( + $this->get_normalized_http_method(), + $this->get_normalized_http_url(), + $this->get_signable_parameters() + ); + + $parts = OAuthUtil::urlencode_rfc3986($parts); + + return implode('&', $parts); + } + + /** + * just uppercases the http method + */ + public function get_normalized_http_method() { + return strtoupper($this->http_method); + } + + /** + * parses the url and rebuilds it to be + * scheme://host/path + */ + public function get_normalized_http_url() { + $parts = parse_url($this->http_url); + + $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; + $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); + $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; + $path = (isset($parts['path'])) ? $parts['path'] : ''; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + return "$scheme://$host$path"; + } + + /** + * builds a url usable for a GET request + */ + public function to_url() { + $post_data = $this->to_postdata(); + $out = $this->get_normalized_http_url(); + if ($post_data) { + $out .= '?'.$post_data; + } + return $out; + } + + /** + * builds the data one would send in a POST request + */ + public function to_postdata() { + return OAuthUtil::build_http_query($this->parameters); + } + + /** + * builds the Authorization: header + */ + public function to_header($realm=null) { + $first = true; + if($realm) { + $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; + $first = false; + } else + $out = 'Authorization: OAuth'; + + $total = array(); + foreach ($this->parameters as $k => $v) { + if (substr($k, 0, 5) != "oauth") continue; + if (is_array($v)) { + throw new OAuthException('arrays not supported in headers'); + } + $out .= ($first) ? ' ' : ','; + $out .= OAuthUtil::urlencode_rfc3986($k) . + '="' . + OAuthUtil::urlencode_rfc3986($v) . + '"'; + $first = false; + } + return $out; + } + + public function __toString() { + return $this->to_url(); + } + + + public function sign_request($signature_method, $consumer, $token) { + $this->set_parameter( + "oauth_signature_method", + $signature_method->get_name(), + false + ); + $signature = $this->build_signature($signature_method, $consumer, $token); + $this->set_parameter("oauth_signature", $signature, false); + } + + public function build_signature($signature_method, $consumer, $token) { + $signature = $signature_method->build_signature($this, $consumer, $token); + return $signature; + } + + /** + * util function: current timestamp + */ + private static function generate_timestamp() { + return time(); + } + + /** + * util function: current nonce + */ + private static function generate_nonce() { + $mt = microtime(); + $rand = mt_rand(); + + return md5($mt . $rand); // md5s look nicer than numbers + } +} + +class OAuthServer { + protected $timestamp_threshold = 300; // in seconds, five minutes + protected $version = '1.0'; // hi blaine + protected $signature_methods = array(); + + protected $data_store; + + function __construct($data_store) { + $this->data_store = $data_store; + } + + public function add_signature_method($signature_method) { + $this->signature_methods[$signature_method->get_name()] = + $signature_method; + } + + // high level functions + + /** + * process a request_token request + * returns the request token on success + */ + public function fetch_request_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // no token required for the initial token request + $token = null; + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $callback = $request->get_parameter('oauth_callback'); + $new_token = $this->data_store->new_request_token($consumer, $callback); + + return $new_token; + } + + /** + * process an access_token request + * returns the access token on success + */ + public function fetch_access_token(&$request) { + $this->get_version($request); + + $consumer = $this->get_consumer($request); + + // requires authorized request token + $token = $this->get_token($request, $consumer, "request"); + + $this->check_signature($request, $consumer, $token); + + // Rev A change + $verifier = $request->get_parameter('oauth_verifier'); + $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); + + return $new_token; + } + + /** + * verify an api call, checks all the parameters + */ + public function verify_request(&$request) { + $this->get_version($request); + $consumer = $this->get_consumer($request); + $token = $this->get_token($request, $consumer, "access"); + $this->check_signature($request, $consumer, $token); + return array($consumer, $token); + } + + // Internals from here + /** + * version 1 + */ + private function get_version(&$request) { + $version = $request->get_parameter("oauth_version"); + if (!$version) { + // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. + // Chapter 7.0 ("Accessing Protected Ressources") + $version = '1.0'; + } + if ($version !== $this->version) { + throw new OAuthException("OAuth version '$version' not supported"); + } + return $version; + } + + /** + * figure out the signature with some defaults + */ + private function get_signature_method($request) { + $signature_method = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_signature_method") + : null; + + if (!$signature_method) { + // According to chapter 7 ("Accessing Protected Ressources") the signature-method + // parameter is required, and we can't just fallback to PLAINTEXT + throw new OAuthException('No signature method parameter. This parameter is required'); + } + + if (!in_array($signature_method, + array_keys($this->signature_methods))) { + throw new OAuthException( + "Signature method '$signature_method' not supported " . + "try one of the following: " . + implode(", ", array_keys($this->signature_methods)) + ); + } + return $this->signature_methods[$signature_method]; + } + + /** + * try to find the consumer for the provided request's consumer key + */ + private function get_consumer($request) { + $consumer_key = $request instanceof OAuthRequest + ? $request->get_parameter("oauth_consumer_key") + : null; + + if (!$consumer_key) { + throw new OAuthException("Invalid consumer key"); + } + + $consumer = $this->data_store->lookup_consumer($consumer_key); + if (!$consumer) { + throw new OAuthException("Invalid consumer"); + } + + return $consumer; + } + + /** + * try to find the token for the provided request's token key + */ + private function get_token($request, $consumer, $token_type="access") { + $token_field = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_token') + : null; + + $token = $this->data_store->lookup_token( + $consumer, $token_type, $token_field + ); + if (!$token) { + throw new OAuthException("Invalid $token_type token: $token_field"); + } + return $token; + } + + /** + * all-in-one function to check the signature on a request + * should guess the signature method appropriately + */ + private function check_signature($request, $consumer, $token) { + // this should probably be in a different method + $timestamp = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_timestamp') + : null; + $nonce = $request instanceof OAuthRequest + ? $request->get_parameter('oauth_nonce') + : null; + + $this->check_timestamp($timestamp); + $this->check_nonce($consumer, $token, $nonce, $timestamp); + + $signature_method = $this->get_signature_method($request); + + $signature = $request->get_parameter('oauth_signature'); + $valid_sig = $signature_method->check_signature( + $request, + $consumer, + $token, + $signature + ); + + if (!$valid_sig) { + throw new OAuthException("Invalid signature"); + } + } + + /** + * check that the timestamp is new enough + */ + private function check_timestamp($timestamp) { + if( ! $timestamp ) + throw new OAuthException( + 'Missing timestamp parameter. The parameter is required' + ); + + // verify that timestamp is recentish + $now = time(); + if (abs($now - $timestamp) > $this->timestamp_threshold) { + throw new OAuthException( + "Expired timestamp, yours $timestamp, ours $now" + ); + } + } + + /** + * check that the nonce is not repeated + */ + private function check_nonce($consumer, $token, $nonce, $timestamp) { + if( ! $nonce ) + throw new OAuthException( + 'Missing nonce parameter. The parameter is required' + ); + + // verify that the nonce is uniqueish + $found = $this->data_store->lookup_nonce( + $consumer, + $token, + $nonce, + $timestamp + ); + if ($found) { + throw new OAuthException("Nonce already used: $nonce"); + } + } + +} + +class OAuthDataStore { + function lookup_consumer($consumer_key) { + // implement me + } + + function lookup_token($consumer, $token_type, $token) { + // implement me + } + + function lookup_nonce($consumer, $token, $nonce, $timestamp) { + // implement me + } + + function new_request_token($consumer, $callback = null) { + // return a new token attached to this consumer + } + + function new_access_token($token, $consumer, $verifier = null) { + // return a new access token attached to this consumer + // for the user associated with this token if the request token + // is authorized + // should also invalidate the request token + } + +} + +class OAuthUtil { + public static function urlencode_rfc3986($input) { + if (is_array($input)) { + return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); + } else if (is_scalar($input)) { + return str_replace( + '+', + ' ', + str_replace('%7E', '~', rawurlencode($input)) + ); + } else { + return ''; + } +} + + + // This decode function isn't taking into consideration the above + // modifications to the encoding process. However, this method doesn't + // seem to be used anywhere so leaving it as is. + public static function urldecode_rfc3986($string) { + return urldecode($string); + } + + // Utility function for turning the Authorization: header into + // parameters, has to do some unescaping + // Can filter out any non-oauth parameters if needed (default behaviour) + // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. + // see http://code.google.com/p/oauth/issues/detail?id=163 + public static function split_header($header, $only_allow_oauth_parameters = true) { + $params = array(); + if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { + foreach ($matches[1] as $i => $h) { + $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); + } + if (isset($params['realm'])) { + unset($params['realm']); + } + } + return $params; + } + + // helper to try to sort out headers for people who aren't running apache + public static function get_headers() { + if (function_exists('apache_request_headers')) { + // we need this to get the actual Authorization: header + // because apache tends to tell us it doesn't exist + $headers = apache_request_headers(); + + // sanitize the output of apache_request_headers because + // we always want the keys to be Cased-Like-This and arh() + // returns the headers in the same case as they are in the + // request + $out = array(); + foreach ($headers AS $key => $value) { + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("-", " ", $key))) + ); + $out[$key] = $value; + } + } else { + // otherwise we don't have apache and are just going to have to hope + // that $_SERVER actually contains what we need + $out = array(); + if( isset($_SERVER['CONTENT_TYPE']) ) + $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; + if( isset($_ENV['CONTENT_TYPE']) ) + $out['Content-Type'] = $_ENV['CONTENT_TYPE']; + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) == "HTTP_") { + // this is chaos, basically it is just there to capitalize the first + // letter of every word that is not an initial HTTP and strip HTTP + // code from przemek + $key = str_replace( + " ", + "-", + ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) + ); + $out[$key] = $value; + } + } + } + return $out; + } + + // This function takes a input like a=b&a=c&d=e and returns the parsed + // parameters like this + // array('a' => array('b','c'), 'd' => 'e') + public static function parse_parameters( $input ) { + if (!isset($input) || !$input) return array(); + + $pairs = explode('&', $input); + + $parsed_parameters = array(); + foreach ($pairs as $pair) { + $split = explode('=', $pair, 2); + $parameter = OAuthUtil::urldecode_rfc3986($split[0]); + $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; + + if (isset($parsed_parameters[$parameter])) { + // We have already recieved parameter(s) with this name, so add to the list + // of parameters with this name + + if (is_scalar($parsed_parameters[$parameter])) { + // This is the first duplicate, so transform scalar (string) into an array + // so we can add the duplicates + $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); + } + + $parsed_parameters[$parameter][] = $value; + } else { + $parsed_parameters[$parameter] = $value; + } + } + return $parsed_parameters; + } + + public static function build_http_query($params) { + if (!$params) return ''; + + // Urlencode both keys and values + $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); + $values = OAuthUtil::urlencode_rfc3986(array_values($params)); + $params = array_combine($keys, $values); + + // Parameters are sorted by name, using lexicographical byte value ordering. + // Ref: Spec: 9.1.1 (1) + uksort($params, 'strcmp'); + + $pairs = array(); + foreach ($params as $parameter => $value) { + if (is_array($value)) { + // If two or more parameters share the same name, they are sorted by their value + // Ref: Spec: 9.1.1 (1) + // June 12th, 2010 - changed to sort because of issue 164 by hidetaka + sort($value, SORT_STRING); + foreach ($value as $duplicate_value) { + $pairs[] = $parameter . '=' . $duplicate_value; + } + } else { + $pairs[] = $parameter . '=' . $value; + } + } + // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) + // Each name-value pair is separated by an '&' character (ASCII code 38) + return implode('&', $pairs); + } +} diff --git a/plugin/social/Hybrid/thirdparty/OAuth/OAuth1Client.php b/plugin/social/Hybrid/thirdparty/OAuth/OAuth1Client.php new file mode 100644 index 000000000..64c03c87d --- /dev/null +++ b/plugin/social/Hybrid/thirdparty/OAuth/OAuth1Client.php @@ -0,0 +1,264 @@ +sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); + $this->consumer = new OAuthConsumer( $consumer_key, $consumer_secret ); + $this->token = null; + + if ( $oauth_token && $oauth_token_secret ){ + $this->token = new OAuthConsumer( $oauth_token, $oauth_token_secret ); + } + } + + /** + * Build authorize url + * + * @return string + */ + function authorizeUrl( $token, $extras =array() ) + { + if ( is_array( $token ) ){ + $token = $token['oauth_token']; + } + + $parameters = array( "oauth_token" => $token ); + + if( count($extras) ) + foreach( $extras as $k=>$v ) + $parameters[$k] = $v; + + return $this->authorize_url . "?" . http_build_query( $parameters ); + } + + /** + * Get a request_token from provider + * + * @return array a key/value array containing oauth_token and oauth_token_secret + */ + function requestToken( $callback = null ) + { + $parameters = array(); + + if ( $callback ) { + $this->redirect_uri = $parameters['oauth_callback'] = $callback; + } + + $request = $this->signedRequest( $this->request_token_url, $this->request_token_method, $parameters ); + $token = OAuthUtil::parse_parameters( $request ); + $this->token = new OAuthConsumer( $token['oauth_token'], $token['oauth_token_secret'] ); + + return $token; + } + + /** + * Exchange the request token and secret for an access token and secret, to sign API calls. + * + * @return array array('oauth_token' => the access token, 'oauth_token_secret' => the access secret) + */ + function accessToken( $oauth_verifier = false, $oauth_token = false ) + { + $parameters = array(); + + // 1.0a + if ( $oauth_verifier ) { + $parameters['oauth_verifier'] = $oauth_verifier; + } + + $request = $this->signedRequest( $this->access_token_url, $this->access_token_method, $parameters ); + $token = OAuthUtil::parse_parameters( $request ); + $this->token = new OAuthConsumer( $token['oauth_token'], $token['oauth_token_secret'] ); + + return $token; + } + + /** + * GET wrapper for provider apis request + */ + function get($url, $parameters = array(), $content_type = null) + { + return $this->api($url, 'GET', $parameters, null, $content_type); + } + + /** + * POST wrapper for provider apis request + */ + function post($url, $parameters = array(), $body = null, $content_type = null, $multipart = false) + { + return $this->api($url, 'POST', $parameters, $body, $content_type, $multipart ); + } + + /** + * Format and sign an oauth for provider api + */ + function api( $url, $method = 'GET', $parameters = array(), $body = null, $content_type = null, $multipart = false ) + { + if ( strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0 ) { + $url = $this->api_base_url . $url; + } + + $response = $this->signedRequest( $url, $method, $parameters, $body, $content_type, $multipart ); + + if( $this->decode_json ){ + $response = json_decode( $response ); + } + + return $this->response = $response; + } + + /** + * Return the response object afer the fact + * + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * Make signed request + */ + function signedRequest( $url, $method, $parameters, $body = null, $content_type = null, $multipart = false ) + { + + $signature_parameters = array(); + + // when making a multipart request, use only oauth_* keys for signature + foreach( $parameters AS $key => $value ){ + if( !$multipart || strpos( $key, 'oauth_' ) === 0 ){ + $signature_parameters[$key] = $value; + } + } + + $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $signature_parameters); + $request->sign_request($this->sha1_method, $this->consumer, $this->token); + switch ($method) { + case 'GET': return $this->request( $request->to_url(), 'GET', null, null, $content_type ); + default : + if ($body) + return $this->request( $request->to_url(), $method, $body, $request->to_header(), $content_type ); + else + return $this->request( $request->get_normalized_http_url(), $method, ($multipart ? $parameters : $request->to_postdata()), $request->to_header(), $content_type, $multipart ) ; + } + } + + /** + * Make http request + */ + function request( $url, $method, $postfields = null, $auth_header = null, $content_type = null, $multipart = false ) + { + Hybrid_Logger::info( "Enter OAuth1Client::request( $method, $url )" ); + Hybrid_Logger::debug( "OAuth1Client::request(). dump post fields: ", serialize( $postfields ) ); + + $this->http_info = array(); + $ci = curl_init(); + + /* Curl settings */ + curl_setopt( $ci, CURLOPT_USERAGENT , $this->curl_useragent ); + curl_setopt( $ci, CURLOPT_CONNECTTIMEOUT, $this->curl_connect_time_out ); + curl_setopt( $ci, CURLOPT_TIMEOUT , $this->curl_time_out ); + curl_setopt( $ci, CURLOPT_RETURNTRANSFER, true ); + curl_setopt( $ci, CURLOPT_HTTPHEADER , array('Expect:') ); + curl_setopt( $ci, CURLOPT_SSL_VERIFYPEER, $this->curl_ssl_verifypeer ); + curl_setopt( $ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader') ); + curl_setopt( $ci, CURLOPT_HEADER , false ); + + if( $multipart ){ + curl_setopt( $ci, CURLOPT_HTTPHEADER, array( 'Expect:', $auth_header ) ); + + }elseif ($content_type) + curl_setopt( $ci, CURLOPT_HTTPHEADER, array('Expect:', "Content-Type: $content_type") ); + + if($this->curl_proxy){ + curl_setopt( $ci, CURLOPT_PROXY , $this->curl_proxy); + } + + switch ($method){ + case 'POST': + curl_setopt( $ci, CURLOPT_POST, true ); + + if ( !empty($postfields) ){ + curl_setopt( $ci, CURLOPT_POSTFIELDS, $postfields ); + } + + if ( !empty($auth_header) && $this->curl_auth_header && !$multipart ){ + curl_setopt( $ci, CURLOPT_HTTPHEADER, array( 'Content-Type: application/atom+xml', $auth_header ) ); + } + break; + case 'DELETE': + curl_setopt( $ci, CURLOPT_CUSTOMREQUEST, 'DELETE' ); + if ( !empty($postfields) ){ + $url = "{$url}?{$postfields}"; + } + } + + curl_setopt($ci, CURLOPT_URL, $url); + $response = curl_exec($ci); + if( $response === false ) { + Hybrid_Logger::error( "OAuth1Client::request(). curl_exec error: ", curl_error($ci) ); + } + + + Hybrid_Logger::debug( "OAuth1Client::request(). dump request info: ", serialize( curl_getinfo($ci) ) ); + Hybrid_Logger::debug( "OAuth1Client::request(). dump request result: ", serialize( $response ) ); + + $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); + + curl_close ($ci); + + return $response; + } + + /** + * Get the header info to store. + */ + function getHeader($ch, $header) { + $i = strpos($header, ':'); + + if ( !empty($i) ){ + $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); + $value = trim(substr($header, $i + 2)); + $this->http_header[$key] = $value; + } + + return strlen($header); + } +} diff --git a/plugin/social/Hybrid/thirdparty/OAuth/OAuth2Client.php b/plugin/social/Hybrid/thirdparty/OAuth/OAuth2Client.php new file mode 100644 index 000000000..0046d2c58 --- /dev/null +++ b/plugin/social/Hybrid/thirdparty/OAuth/OAuth2Client.php @@ -0,0 +1,302 @@ +client_id = $client_id; + $this->client_secret = $client_secret; + $this->redirect_uri = $redirect_uri; + $this->curl_compressed = $compressed; + } + + public function authorizeUrl( $extras = array() ) + { + $params = array( + "client_id" => $this->client_id, + "redirect_uri" => $this->redirect_uri, + "response_type" => "code" + ); + + if( count($extras) ) + foreach( $extras as $k=>$v ) + $params[$k] = $v; + + return $this->authorize_url . "?" . http_build_query($params, '', '&'); + } + + public function authenticate( $code ) + { + $params = array( + "client_id" => $this->client_id, + "client_secret" => $this->client_secret, + "grant_type" => "authorization_code", + "redirect_uri" => $this->redirect_uri, + "code" => $code + ); + + $response = $this->request( $this->token_url, $params, $this->curl_authenticate_method ); + + $response = $this->parseRequestResult( $response ); + + if( ! $response || ! isset( $response->access_token ) ){ + throw new Exception( "The Authorization Service has return: " . $response->error ); + } + + if( isset( $response->access_token ) ) $this->access_token = $response->access_token; + if( isset( $response->refresh_token ) ) $this->refresh_token = $response->refresh_token; + if( isset( $response->expires_in ) ) $this->access_token_expires_in = $response->expires_in; + + // calculate when the access token expire + if( isset($response->expires_in)) { + $this->access_token_expires_at = time() + $response->expires_in; + } + + return $response; + } + + public function authenticated() + { + if ( $this->access_token ){ + if ( $this->token_info_url && $this->refresh_token ){ + // check if this access token has expired, + $tokeninfo = $this->tokenInfo( $this->access_token ); + + // if yes, access_token has expired, then ask for a new one + if( $tokeninfo && isset( $tokeninfo->error ) ){ + $response = $this->refreshToken( $this->refresh_token ); + + // if wrong response + if( ! isset( $response->access_token ) || ! $response->access_token ){ + throw new Exception( "The Authorization Service has return an invalid response while requesting a new access token. given up!" ); + } + + // set new access_token + $this->access_token = $response->access_token; + } + } + + return true; + } + + return false; + } + + /** + * Format and sign an oauth for provider api + */ + public function api( $url, $method = "GET", $parameters = array(), $decode_json = true ) + { + if ( strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0 ) { + $url = $this->api_base_url . $url; + } + + $parameters[$this->sign_token_name] = $this->access_token; + $response = null; + + switch( $method ){ + case 'GET' : $response = $this->request( $url, $parameters, "GET" ); break; + case 'POST' : $response = $this->request( $url, $parameters, "POST" ); break; + case 'DELETE' : $response = $this->request( $url, $parameters, "DELETE" ); break; + case 'PATCH' : $response = $this->request( $url, $parameters, "PATCH" ); break; + } + + if( $response && $decode_json ){ + return $this->response = json_decode( $response ); + } + + return $this->response = $response; + } + + /** + * Return the response object afer the fact + * + * @return mixed + */ + public function getResponse() + { + return $this->response; + } + + /** + * GET wrapper for provider apis request + */ + function get( $url, $parameters = array(), $decode_json = true ) + { + return $this->api( $url, 'GET', $parameters, $decode_json ); + } + + /** + * POST wrapper for provider apis request + */ + function post( $url, $parameters = array(), $decode_json = true ) + { + return $this->api( $url, 'POST', $parameters, $decode_json ); + } + + // -- tokens + + public function tokenInfo($accesstoken) + { + $params['access_token'] = $this->access_token; + $response = $this->request( $this->token_info_url, $params ); + return $this->parseRequestResult( $response ); + } + + public function refreshToken( $parameters = array() ) + { + $params = array( + "client_id" => $this->client_id, + "client_secret" => $this->client_secret, + "grant_type" => "refresh_token" + ); + + foreach($parameters as $k=>$v ){ + $params[$k] = $v; + } + + $response = $this->request( $this->token_url, $params, "POST" ); + return $this->parseRequestResult( $response ); + } + + // -- utilities + + private function request( $url, $params=false, $type="GET" ) + { + Hybrid_Logger::info( "Enter OAuth2Client::request( $url )" ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request params: ", serialize( $params ) ); + + $urlEncodedParams = http_build_query($params, '', '&'); + + if( $type == "GET" ){ + $url = $url . ( strpos( $url, '?' ) ? '&' : '?' ) . $urlEncodedParams; + } + + $this->http_info = array(); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL , $url ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1 ); + curl_setopt($ch, CURLOPT_TIMEOUT , $this->curl_time_out ); + curl_setopt($ch, CURLOPT_USERAGENT , $this->curl_useragent ); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT , $this->curl_connect_time_out ); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER , $this->curl_ssl_verifypeer ); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST , $this->curl_ssl_verifyhost ); + curl_setopt($ch, CURLOPT_HTTPHEADER , $this->curl_header ); + + if ($this->curl_compressed){ + curl_setopt($ch, CURLOPT_ENCODING, "gzip,deflate"); + } + + if($this->curl_proxy){ + curl_setopt( $ch, CURLOPT_PROXY , $this->curl_proxy); + } + + if ($type == "POST") { + curl_setopt($ch, CURLOPT_POST, 1); + + // If request body exists then encode it for "application/json". + if (isset($params['body'])) { + $urlEncodedParams = json_encode($params['body']); + } + + // Using URL encoded params here instead of a more convenient array + // cURL will set a wrong HTTP Content-Type header if using an array (cf. http://www.php.net/manual/en/function.curl-setopt.php, Notes section for "CURLOPT_POSTFIELDS") + // OAuth requires application/x-www-form-urlencoded Content-Type (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1) + if ($params) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $urlEncodedParams); + } + } + + if( $type == "DELETE" ){ + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + if( $type == "PATCH" ){ + curl_setopt($ch, CURLOPT_POST, 1); + if($params) curl_setopt( $ch, CURLOPT_POSTFIELDS, $params ); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); + } + $response = curl_exec($ch); + if( $response === false ) { + Hybrid_Logger::error( "OAuth2Client::request(). curl_exec error: ", curl_error($ch) ); + } + Hybrid_Logger::debug( "OAuth2Client::request(). dump request info: ", serialize( curl_getinfo($ch) ) ); + Hybrid_Logger::debug( "OAuth2Client::request(). dump request result: ", serialize( $response ) ); + + $this->http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->http_info = array_merge($this->http_info, curl_getinfo($ch)); + + curl_close ($ch); + + return $response; + } + + private function parseRequestResult( $result ) + { + if( json_decode( $result ) ) return json_decode( $result ); + + parse_str( $result, $output ); + + $result = new StdClass(); + + foreach( $output as $k => $v ) + $result->$k = $v; + + return $result; + } + /** + * DELETE wrapper for provider apis request + */ + function delete( $url, $parameters = array() ) + { + return $this->api( $url, 'DELETE', $parameters ); + } + /** + * PATCH wrapper for provider apis request + */ + function patch( $url, $parameters = array() ) + { + return $this->api( $url, 'PATCH', $parameters ); + } +} diff --git a/plugin/social/Hybrid/thirdparty/OpenID/LightOpenID.php b/plugin/social/Hybrid/thirdparty/OpenID/LightOpenID.php new file mode 100644 index 000000000..b1cb41bb3 --- /dev/null +++ b/plugin/social/Hybrid/thirdparty/OpenID/LightOpenID.php @@ -0,0 +1,1051 @@ += 5.1.2 with cURL or HTTP/HTTPS stream wrappers enabled. + * + * @version v1.2.0 (2014-01-14) + * @link https://code.google.com/p/lightopenid/ Project URL + * @link https://github.com/iignatov/LightOpenID GitHub Repo + * @author Mewp + * @copyright Copyright (c) 2013 Mewp + * @license http://opensource.org/licenses/mit-license.php MIT License + */ +class LightOpenID +{ + public $returnUrl + , $required = array() + , $optional = array() + , $verify_peer = null + , $capath = null + , $cainfo = null + , $cnmatch = null + , $data + , $oauth = array() + , $curl_time_out = 30 + , $curl_connect_time_out = 30; + private $identity, $claimed_id; + protected $server, $version, $trustRoot, $aliases, $identifier_select = false + , $ax = false, $sreg = false, $setup_url = null, $headers = array() + , $proxy = null, $user_agent = 'LightOpenID' + , $xrds_override_pattern = null, $xrds_override_replacement = null; + static protected $ax_to_sreg = array( + 'namePerson/friendly' => 'nickname', + 'contact/email' => 'email', + 'namePerson' => 'fullname', + 'birthDate' => 'dob', + 'person/gender' => 'gender', + 'contact/postalCode/home' => 'postcode', + 'contact/country/home' => 'country', + 'pref/language' => 'language', + 'pref/timezone' => 'timezone', + ); + + function __construct($host, $proxy = null) + { + $this->set_realm($host); + $this->set_proxy($proxy); + + $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); + $this->returnUrl = $this->trustRoot . $uri; + + $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; + + if(!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { + throw new ErrorException('You must have either https wrappers or curl enabled.'); + } + } + + function __isset($name) + { + return in_array($name, array('identity', 'trustRoot', 'realm', 'xrdsOverride', 'mode')); + } + + function __set($name, $value) + { + switch ($name) { + case 'identity': + if (strlen($value = trim((String) $value))) { + if (preg_match('#^xri:/*#i', $value, $m)) { + $value = substr($value, strlen($m[0])); + } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { + $value = "http://$value"; + } + if (preg_match('#^https?://[^/]+$#i', $value, $m)) { + $value .= '/'; + } + } + $this->$name = $this->claimed_id = $value; + break; + case 'trustRoot': + case 'realm': + $this->trustRoot = trim($value); + break; + case 'xrdsOverride': + if (is_array($value)) { + list($pattern, $replacement) = $value; + $this->xrds_override_pattern = $pattern; + $this->xrds_override_replacement = $replacement; + } else { + trigger_error('Invalid value specified for "xrdsOverride".', E_USER_ERROR); + } + break; + } + } + + function __get($name) + { + switch ($name) { + case 'identity': + # We return claimed_id instead of identity, + # because the developer should see the claimed identifier, + # i.e. what he set as identity, not the op-local identifier (which is what we verify) + return $this->claimed_id; + case 'trustRoot': + case 'realm': + return $this->trustRoot; + case 'mode': + return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; + } + } + + function set_proxy($proxy) + { + if (!empty($proxy)) { + // When the proxy is a string - try to parse it. + if (!is_array($proxy)) { + $proxy = parse_url($proxy); + } + + // Check if $proxy is valid after the parsing. + if ($proxy && !empty($proxy['host'])) { + // Make sure that a valid port number is specified. + if (array_key_exists('port', $proxy)) { + if (!is_int($proxy['port'])) { + $proxy['port'] = is_numeric($proxy['port']) ? intval($proxy['port']) : 0; + } + + if ($proxy['port'] <= 0) { + throw new ErrorException('The specified proxy port number is invalid.'); + } + } + + $this->proxy = $proxy; + } + } + } + + /** + * Checks if the server specified in the url exists. + * + * @param $url url to check + * @return true, if the server exists; false otherwise + */ + function hostExists($url) + { + if (strpos($url, '/') === false) { + $server = $url; + } else { + $server = @parse_url($url, PHP_URL_HOST); + } + + if (!$server) { + return false; + } + + return !!gethostbynamel($server); + } + + protected function set_realm($uri) + { + $realm = ''; + + # Set a protocol, if not specified. + $realm .= (($offset = strpos($uri, '://')) === false) ? $this->get_realm_protocol() : ''; + + # Set the offset properly. + $offset = (($offset !== false) ? $offset + 3 : 0); + + # Get only the root, without the path. + $realm .= (($end = strpos($uri, '/', $offset)) === false) ? $uri : substr($uri, 0, $end); + + $this->trustRoot = $realm; + } + + protected function get_realm_protocol() + { + if (!empty($_SERVER['HTTPS'])) { + $use_secure_protocol = ($_SERVER['HTTPS'] != 'off'); + } else if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $use_secure_protocol = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); + } else { + $use_secure_protocol = false; + } + + return $use_secure_protocol ? 'https://' : 'http://'; + } + + protected function request_curl($url, $method='GET', $params=array(), $update_claimed_id) + { + $params = http_build_query($params, '', '&'); + $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_time_out); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT , $this->curl_connect_time_out); + + + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); + } else { + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); + } + + if (!empty($this->proxy)) { + curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); + + if (!empty($this->proxy['port'])) { + curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); + } + + if (!empty($this->proxy['user'])) { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); + } + } + + if($this->verify_peer !== null) { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); + if($this->capath) { + curl_setopt($curl, CURLOPT_CAPATH, $this->capath); + } + + if($this->cainfo) { + curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); + } + } + + if ($method == 'POST') { + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $params); + } elseif ($method == 'HEAD') { + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_NOBODY, true); + } else { + curl_setopt($curl, CURLOPT_HEADER, true); + curl_setopt($curl, CURLOPT_HTTPGET, true); + } + $response = curl_exec($curl); + + if($method == 'HEAD' && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 405) { + curl_setopt($curl, CURLOPT_HTTPGET, true); + $response = curl_exec($curl); + $response = substr($response, 0, strpos($response, "\r\n\r\n")); + } + + if($method == 'HEAD' || $method == 'GET') { + $header_response = $response; + + # If it's a GET request, we want to only parse the header part. + if($method == 'GET') { + $header_response = substr($response, 0, strpos($response, "\r\n\r\n")); + } + + $headers = array(); + foreach(explode("\n", $header_response) as $header) { + $pos = strpos($header,':'); + if ($pos !== false) { + $name = strtolower(trim(substr($header, 0, $pos))); + $headers[$name] = trim(substr($header, $pos+1)); + } + } + + if($update_claimed_id) { + # Update the claimed_id value in case of redirections. + $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); + # Ignore the fragment (some cURL versions don't handle it well). + if (strtok($effective_url, '#') != strtok($url, '#')) { + $this->identity = $this->claimed_id = $effective_url; + } + } + + if($method == 'HEAD') { + return $headers; + } else { + $this->headers = $headers; + } + } + + if (curl_errno($curl)) { + throw new ErrorException(curl_error($curl), curl_errno($curl)); + } + + return $response; + } + + protected function parse_header_array($array, $update_claimed_id) + { + $headers = array(); + foreach($array as $header) { + $pos = strpos($header,':'); + if ($pos !== false) { + $name = strtolower(trim(substr($header, 0, $pos))); + $headers[$name] = trim(substr($header, $pos+1)); + + # Following possible redirections. The point is just to have + # claimed_id change with them, because the redirections + # are followed automatically. + # We ignore redirections with relative paths. + # If any known provider uses them, file a bug report. + if($name == 'location' && $update_claimed_id) { + if(strpos($headers[$name], 'http') === 0) { + $this->identity = $this->claimed_id = $headers[$name]; + } elseif($headers[$name][0] == '/') { + $parsed_url = parse_url($this->claimed_id); + $this->identity = + $this->claimed_id = $parsed_url['scheme'] . '://' + . $parsed_url['host'] + . $headers[$name]; + } + } + } + } + return $headers; + } + + protected function request_streams($url, $method='GET', $params=array(), $update_claimed_id) + { + if(!$this->hostExists($url)) { + throw new ErrorException("Could not connect to $url.", 404); + } + + if (empty($this->cnmatch)) { + $this->cnmatch = parse_url($url, PHP_URL_HOST); + } + + $params = http_build_query($params, '', '&'); + switch($method) { + case 'GET': + $opts = array( + 'http' => array( + 'method' => 'GET', + 'header' => 'Accept: application/xrds+xml, */*', + 'user_agent' => $this->user_agent, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + $url = $url . ($params ? '?' . $params : ''); + if (!empty($this->proxy)) { + $opts['http']['proxy'] = $this->proxy_url(); + } + break; + case 'POST': + $opts = array( + 'http' => array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'user_agent' => $this->user_agent, + 'content' => $params, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + if (!empty($this->proxy)) { + $opts['http']['proxy'] = $this->proxy_url(); + } + break; + case 'HEAD': + // We want to send a HEAD request, but since get_headers() doesn't + // accept $context parameter, we have to change the defaults. + $default = stream_context_get_options(stream_context_get_default()); + + // PHP does not reset all options. Instead, it just sets the options + // available in the passed array, therefore set the defaults manually. + $default += array( + 'http' => array(), + 'ssl' => array() + ); + $default['http'] += array( + 'method' => 'GET', + 'header' => '', + 'user_agent' => '', + 'ignore_errors' => false + ); + $default['ssl'] += array( + 'CN_match' => '' + ); + + $opts = array( + 'http' => array( + 'method' => 'HEAD', + 'header' => 'Accept: application/xrds+xml, */*', + 'user_agent' => $this->user_agent, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'CN_match' => $this->cnmatch + ) + ); + + // Enable validation of the SSL certificates. + if ($this->verify_peer) { + $default['ssl'] += array( + 'verify_peer' => false, + 'capath' => '', + 'cafile' => '' + ); + $opts['ssl'] += array( + 'verify_peer' => true, + 'capath' => $this->capath, + 'cafile' => $this->cainfo + ); + } + + // Change the stream context options. + stream_context_get_default($opts); + + $headers = get_headers($url . ($params ? '?' . $params : '')); + + // Restore the stream context options. + stream_context_get_default($default); + + if (!empty($headers)) { + if (intval(substr($headers[0], strlen('HTTP/1.1 '))) == 405) { + // The server doesn't support HEAD - emulate it with a GET. + $args = func_get_args(); + $args[1] = 'GET'; + call_user_func_array(array($this, 'request_streams'), $args); + $headers = $this->headers; + } else { + $headers = $this->parse_header_array($headers, $update_claimed_id); + } + } else { + $headers = array(); + } + + return $headers; + } + + if ($this->verify_peer) { + $opts['ssl'] += array( + 'verify_peer' => true, + 'capath' => $this->capath, + 'cafile' => $this->cainfo + ); + } + + $context = stream_context_create ($opts); + $data = file_get_contents($url, false, $context); + # This is a hack for providers who don't support HEAD requests. + # It just creates the headers array for the last request in $this->headers. + if(isset($http_response_header)) { + $this->headers = $this->parse_header_array($http_response_header, $update_claimed_id); + } + + return $data; + } + + protected function request($url, $method='GET', $params=array(), $update_claimed_id=false) + { + $use_curl = false; + + if (function_exists('curl_init')) { + if (!$use_curl) { + # When allow_url_fopen is disabled, PHP streams will not work. + $use_curl = !ini_get('allow_url_fopen'); + } + + if (!$use_curl) { + # When there is no HTTPS wrapper, PHP streams cannott be used. + $use_curl = !in_array('https', stream_get_wrappers()); + } + + if (!$use_curl) { + # With open_basedir or safe_mode set, cURL can't follow redirects. + $use_curl = !(ini_get('safe_mode') || ini_get('open_basedir')); + } + } + + return + $use_curl + ? $this->request_curl($url, $method, $params, $update_claimed_id) + : $this->request_streams($url, $method, $params, $update_claimed_id); + } + + protected function proxy_url() + { + $result = ''; + + if (!empty($this->proxy)) { + $result = $this->proxy['host']; + + if (!empty($this->proxy['port'])) { + $result = $result . ':' . $this->proxy['port']; + } + + if (!empty($this->proxy['user'])) { + $result = $this->proxy['user'] . ':' . $this->proxy['pass'] . '@' . $result; + } + + $result = 'http://' . $result; + } + + return $result; + } + + protected function build_url($url, $parts) + { + if (isset($url['query'], $parts['query'])) { + $parts['query'] = $url['query'] . '&' . $parts['query']; + } + + $url = $parts + $url; + $url = $url['scheme'] . '://' + . (empty($url['username'])?'' + :(empty($url['password'])? "{$url['username']}@" + :"{$url['username']}:{$url['password']}@")) + . $url['host'] + . (empty($url['port'])?'':":{$url['port']}") + . (empty($url['path'])?'':$url['path']) + . (empty($url['query'])?'':"?{$url['query']}") + . (empty($url['fragment'])?'':"#{$url['fragment']}"); + return $url; + } + + /** + * Helper function used to scan for / tags and extract information + * from them + */ + protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) + { + preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); + preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); + + $result = array_merge($matches1[1], $matches2[1]); + return empty($result)?false:$result[0]; + } + + /** + * Performs Yadis and HTML discovery. Normally not used. + * @param $url Identity URL. + * @return String OP Endpoint (i.e. OpenID provider address). + * @throws ErrorException + */ + function discover($url) + { + if (!$url) throw new ErrorException('No identity supplied.'); + # Use xri.net proxy to resolve i-name identities + if (!preg_match('#^https?:#', $url)) { + $url = "https://xri.net/$url"; + } + + # We save the original url in case of Yadis discovery failure. + # It can happen when we'll be lead to an XRDS document + # which does not have any OpenID2 services. + $originalUrl = $url; + + # A flag to disable yadis discovery in case of failure in headers. + $yadis = true; + + # Allows optional regex replacement of the URL, e.g. to use Google Apps + # as an OpenID provider without setting up XRDS on the domain hosting. + if (!is_null($this->xrds_override_pattern) && !is_null($this->xrds_override_replacement)) { + $url = preg_replace($this->xrds_override_pattern, $this->xrds_override_replacement, $url); + } + + # We'll jump a maximum of 5 times, to avoid endless redirections. + for ($i = 0; $i < 5; $i ++) { + if ($yadis) { + $headers = $this->request($url, 'HEAD', array(), true); + + $next = false; + if (isset($headers['x-xrds-location'])) { + $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); + $next = true; + } + + if (isset($headers['content-type']) && $this->is_allowed_type($headers['content-type'])) { + # Found an XRDS document, now let's find the server, and optionally delegate. + $content = $this->request($url, 'GET'); + + preg_match_all('#(.*?)#s', $content, $m); + foreach($m[1] as $content) { + $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. + + # OpenID 2 + $ns = preg_quote('http://specs.openid.net/auth/2.0/', '#'); + if(preg_match('#\s*'.$ns.'(server|signon)\s*#s', $content, $type)) { + if ($type[1] == 'server') $this->identifier_select = true; + + preg_match('#(.*)#', $content, $server); + preg_match('#<(Local|Canonical)ID>(.*)#', $content, $delegate); + if (empty($server)) { + return false; + } + # Does the server advertise support for either AX or SREG? + $this->ax = (bool) strpos($content, 'http://openid.net/srv/ax/1.0'); + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); + + $server = $server[1]; + if (isset($delegate[2])) $this->identity = trim($delegate[2]); + $this->version = 2; + + $this->server = $server; + return $server; + } + + # OpenID 1.1 + $ns = preg_quote('http://openid.net/signon/1.1', '#'); + if (preg_match('#\s*'.$ns.'\s*#s', $content)) { + + preg_match('#(.*)#', $content, $server); + preg_match('#<.*?Delegate>(.*)#', $content, $delegate); + if (empty($server)) { + return false; + } + # AX can be used only with OpenID 2.0, so checking only SREG + $this->sreg = strpos($content, 'http://openid.net/sreg/1.0') + || strpos($content, 'http://openid.net/extensions/sreg/1.1'); + + $server = $server[1]; + if (isset($delegate[1])) $this->identity = $delegate[1]; + $this->version = 1; + + $this->server = $server; + return $server; + } + } + + $next = true; + $yadis = false; + $url = $originalUrl; + $content = null; + break; + } + if ($next) continue; + + # There are no relevant information in headers, so we search the body. + $content = $this->request($url, 'GET', array(), true); + + if (isset($this->headers['x-xrds-location'])) { + $url = $this->build_url(parse_url($url), parse_url(trim($this->headers['x-xrds-location']))); + continue; + } + + $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); + if ($location) { + $url = $this->build_url(parse_url($url), parse_url($location)); + continue; + } + } + + if (!$content) $content = $this->request($url, 'GET'); + + # At this point, the YADIS Discovery has failed, so we'll switch + # to openid2 HTML discovery, then fallback to openid 1.1 discovery. + $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); + $this->version = 2; + + if (!$server) { + # The same with openid 1.1 + $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); + $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); + $this->version = 1; + } + + if ($server) { + # We found an OpenID2 OP Endpoint + if ($delegate) { + # We have also found an OP-Local ID. + $this->identity = $delegate; + } + $this->server = $server; + return $server; + } + + throw new ErrorException("No OpenID Server found at $url", 404); + } + throw new ErrorException('Endless redirection!', 500); + } + + protected function is_allowed_type($content_type) { + # Apparently, some providers return XRDS documents as text/html. + # While it is against the spec, allowing this here shouldn't break + # compatibility with anything. + $allowed_types = array('application/xrds+xml', 'text/html', 'text/xml'); + + foreach ($allowed_types as $type) { + if (strpos($content_type, $type) !== false) { + return true; + } + } + + return false; + } + + protected function sregParams() + { + $params = array(); + # We always use SREG 1.1, even if the server is advertising only support for 1.0. + # That's because it's fully backwards compatibile with 1.0, and some providers + # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com + $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; + if ($this->required) { + $params['openid.sreg.required'] = array(); + foreach ($this->required as $required) { + if (!isset(self::$ax_to_sreg[$required])) continue; + $params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; + } + $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); + } + + if ($this->optional) { + $params['openid.sreg.optional'] = array(); + foreach ($this->optional as $optional) { + if (!isset(self::$ax_to_sreg[$optional])) continue; + $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; + } + $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); + } + return $params; + } + + protected function axParams() + { + $params = array(); + if ($this->required || $this->optional) { + $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; + $params['openid.ax.mode'] = 'fetch_request'; + $this->aliases = array(); + $counts = array(); + $required = array(); + $optional = array(); + foreach (array('required','optional') as $type) { + foreach ($this->$type as $alias => $field) { + if (is_int($alias)) $alias = strtr($field, '/', '_'); + $this->aliases[$alias] = 'http://axschema.org/' . $field; + if (empty($counts[$alias])) $counts[$alias] = 0; + $counts[$alias] += 1; + ${$type}[] = $alias; + } + } + foreach ($this->aliases as $alias => $ns) { + $params['openid.ax.type.' . $alias] = $ns; + } + foreach ($counts as $alias => $count) { + if ($count == 1) continue; + $params['openid.ax.count.' . $alias] = $count; + } + + # Don't send empty ax.requied and ax.if_available. + # Google and possibly other providers refuse to support ax when one of these is empty. + if($required) { + $params['openid.ax.required'] = implode(',', $required); + } + if($optional) { + $params['openid.ax.if_available'] = implode(',', $optional); + } + } + return $params; + } + + protected function authUrl_v1($immediate) + { + $returnUrl = $this->returnUrl; + # If we have an openid.delegate that is different from our claimed id, + # we need to somehow preserve the claimed id between requests. + # The simplest way is to just send it along with the return_to url. + if($this->identity != $this->claimed_id) { + $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; + } + + $params = array( + 'openid.return_to' => $returnUrl, + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', + 'openid.identity' => $this->identity, + 'openid.trust_root' => $this->trustRoot, + ) + $this->sregParams(); + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + protected function authUrl_v2($immediate) + { + $params = array( + 'openid.ns' => 'http://specs.openid.net/auth/2.0', + 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', + 'openid.return_to' => $this->returnUrl, + 'openid.realm' => $this->trustRoot, + ); + + if ($this->ax) { + $params += $this->axParams(); + } + + if ($this->sreg) { + $params += $this->sregParams(); + } + + if (!$this->ax && !$this->sreg) { + # If OP doesn't advertise either SREG, nor AX, let's send them both + # in worst case we don't get anything in return. + $params += $this->axParams() + $this->sregParams(); + } + + if (!empty($this->oauth) && is_array($this->oauth)) { + $params['openid.ns.oauth'] = 'http://specs.openid.net/extensions/oauth/1.0'; + $params['openid.oauth.consumer'] = str_replace(array('http://', 'https://'), '', $this->trustRoot); + $params['openid.oauth.scope'] = implode(' ', $this->oauth); + } + + if ($this->identifier_select) { + $params['openid.identity'] = $params['openid.claimed_id'] + = 'http://specs.openid.net/auth/2.0/identifier_select'; + } else { + $params['openid.identity'] = $this->identity; + $params['openid.claimed_id'] = $this->claimed_id; + } + + return $this->build_url(parse_url($this->server) + , array('query' => http_build_query($params, '', '&'))); + } + + /** + * Returns authentication url. Usually, you want to redirect your user to it. + * @return String The authentication url. + * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1. + * @throws ErrorException + */ + function authUrl($immediate = false) + { + if ($this->setup_url && !$immediate) return $this->setup_url; + if (!$this->server) $this->discover($this->identity); + + if ($this->version == 2) { + return $this->authUrl_v2($immediate); + } + return $this->authUrl_v1($immediate); + } + + /** + * Performs OpenID verification with the OP. + * @return Bool Whether the verification was successful. + * @throws ErrorException + */ + function validate() + { + # If the request was using immediate mode, a failure may be reported + # by presenting user_setup_url (for 1.1) or reporting + # mode 'setup_needed' (for 2.0). Also catching all modes other than + # id_res, in order to avoid throwing errors. + if(isset($this->data['openid_user_setup_url'])) { + $this->setup_url = $this->data['openid_user_setup_url']; + return false; + } + if($this->mode != 'id_res') { + return false; + } + + $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; + $params = array( + 'openid.assoc_handle' => $this->data['openid_assoc_handle'], + 'openid.signed' => $this->data['openid_signed'], + 'openid.sig' => $this->data['openid_sig'], + ); + + if (isset($this->data['openid_ns'])) { + # We're dealing with an OpenID 2.0 server, so let's set an ns + # Even though we should know location of the endpoint, + # we still need to verify it by discovery, so $server is not set here + $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; + } elseif (isset($this->data['openid_claimed_id']) + && $this->data['openid_claimed_id'] != $this->data['openid_identity'] + ) { + # If it's an OpenID 1 provider, and we've got claimed_id, + # we have to append it to the returnUrl, like authUrl_v1 does. + $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') + . 'openid.claimed_id=' . $this->claimed_id; + } + + if ($this->data['openid_return_to'] != $this->returnUrl) { + # The return_to url must match the url of current request. + # I'm assuing that noone will set the returnUrl to something that doesn't make sense. + return false; + } + + $server = $this->discover($this->claimed_id); + + foreach (explode(',', $this->data['openid_signed']) as $item) { + # Checking whether magic_quotes_gpc is turned on, because + # the function may fail if it is. For example, when fetching + # AX namePerson, it might containg an apostrophe, which will be escaped. + # In such case, validation would fail, since we'd send different data than OP + # wants to verify. stripslashes() should solve that problem, but we can't + # use it when magic_quotes is off. + $value = $this->data['openid_' . str_replace('.','_',$item)]; + $params['openid.' . $item] = function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ? stripslashes($value) : $value; + + } + + $params['openid.mode'] = 'check_authentication'; + + $response = $this->request($server, 'POST', $params); + + return preg_match('/is_valid\s*:\s*true/i', $response); + } + + protected function getAxAttributes() + { + $result = array(); + + if ($alias = $this->getNamespaceAlias('http://openid.net/srv/ax/1.0', 'ax')) { + $prefix = 'openid_' . $alias; + $length = strlen('http://axschema.org/'); + + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = $alias . '.type.'; + + if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { + continue; + } + + $key = substr($key, strlen($keyMatch)); + $idv = $prefix . '_value_' . $key; + $idc = $prefix . '_count_' . $key; + $key = substr($this->getItem($prefix . '_type_' . $key), $length); + + if (!empty($key)) { + if (($count = intval($this->getItem($idc))) > 0) { + $value = array(); + + for ($i = 1; $i <= $count; $i++) { + $value[] = $this->getItem($idv . '_' . $i); + } + + $value = ($count == 1) ? reset($value) : $value; + } else { + $value = $this->getItem($idv); + } + + if (!is_null($value)) { + $result[$key] = $value; + } + } + } + } else { + // No alias for the AX schema has been found, + // so there is no AX data in the OP's response. + } + + return $result; + } + + protected function getSregAttributes() + { + $attributes = array(); + $sreg_to_ax = array_flip(self::$ax_to_sreg); + foreach (explode(',', $this->data['openid_signed']) as $key) { + $keyMatch = 'sreg.'; + if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { + continue; + } + $key = substr($key, strlen($keyMatch)); + if (!isset($sreg_to_ax[$key])) { + # The field name isn't part of the SREG spec, so we ignore it. + continue; + } + $attributes[$sreg_to_ax[$key]] = $this->data['openid_sreg_' . $key]; + } + return $attributes; + } + + /** + * Gets AX/SREG attributes provided by OP. should be used only after successful validaton. + * Note that it does not guarantee that any of the required/optional parameters will be present, + * or that there will be no other attributes besides those specified. + * In other words. OP may provide whatever information it wants to. + * * SREG names will be mapped to AX names. + * * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email' + * @see http://www.axschema.org/types/ + */ + function getAttributes() + { + if (isset($this->data['openid_ns']) + && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' + ) { # OpenID 2.0 + # We search for both AX and SREG attributes, with AX taking precedence. + return $this->getAxAttributes() + $this->getSregAttributes(); + } + return $this->getSregAttributes(); + } + + /** + * Gets an OAuth request token if the OpenID+OAuth hybrid protocol has been used. + * + * In order to use the OpenID+OAuth hybrid protocol, you need to add at least one + * scope to the $openid->oauth array before you get the call to getAuthUrl(), e.g.: + * $openid->oauth[] = 'https://www.googleapis.com/auth/plus.me'; + * + * Furthermore the registered consumer name must fit the OpenID realm. + * To register an OpenID consumer at Google use: https://www.google.com/accounts/ManageDomains + * + * @return string|bool OAuth request token on success, FALSE if no token was provided. + */ + function getOAuthRequestToken() + { + $alias = $this->getNamespaceAlias('http://specs.openid.net/extensions/oauth/1.0'); + + return !empty($alias) ? $this->data['openid_' . $alias . '_request_token'] : false; + } + + /** + * Gets the alias for the specified namespace, if it's present. + * + * @param string $namespace The namespace for which an alias is needed. + * @param string $hint Common alias of this namespace, used for optimization. + * @return string|null The namespace alias if found, otherwise - NULL. + */ + private function getNamespaceAlias($namespace, $hint = null) + { + $result = null; + + if (empty($hint) || $this->getItem('openid_ns_' . $hint) != $namespace) { + // The common alias is either undefined or points to + // some other extension - search for another alias.. + $prefix = 'openid_ns_'; + $length = strlen($prefix); + + foreach ($this->data as $key => $val) { + if (strncmp($key, $prefix, $length) === 0 && $val === $namespace) { + $result = trim(substr($key, $length)); + break; + } + } + } else { + $result = $hint; + } + + return $result; + } + + /** + * Gets an item from the $data array by the specified id. + * + * @param string $id The id of the desired item. + * @return string|null The item if found, otherwise - NULL. + */ + private function getItem($id) + { + return isset($this->data[$id]) ? $this->data[$id] : null; + } +} diff --git a/plugin/social/Hybrid/thirdparty/index.html b/plugin/social/Hybrid/thirdparty/index.html new file mode 100644 index 000000000..c942a79ce --- /dev/null +++ b/plugin/social/Hybrid/thirdparty/index.html @@ -0,0 +1,10 @@ + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + \ No newline at end of file diff --git a/plugin/social/_common.php b/plugin/social/_common.php new file mode 100644 index 000000000..bad54a5d7 --- /dev/null +++ b/plugin/social/_common.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/plugin/social/config.php b/plugin/social/config.php new file mode 100644 index 000000000..247e396bb --- /dev/null +++ b/plugin/social/config.php @@ -0,0 +1,62 @@ + "http://localhost/hybridauth-git/hybridauth/", + "providers" => array( + // openid providers + "OpenID" => array( + "enabled" => true, + ), + "Yahoo" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + ), + "AOL" => array( + "enabled" => true, + ), + "Google" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + ), + "Facebook" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + "trustForwarded" => false, + ), + "Twitter" => array( + "enabled" => true, + "keys" => array("key" => "", "secret" => ""), + "includeEmail" => false, + ), + // windows live + "Live" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + ), + "LinkedIn" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + "fields" => array(), + ), + "Foursquare" => array( + "enabled" => true, + "keys" => array("id" => "", "secret" => ""), + ), + ), + // If you want to enable logging, set 'debug_mode' to true. + // You can also set it to + // - "error" To log only error messages. Useful in production + // - "info" To log info and error messages (ignore debug messages) + "debug_mode" => false, + // Path to file writable by the web server. Required if 'debug_mode' is not false + "debug_file" => "", +); diff --git a/plugin/social/error.php b/plugin/social/error.php new file mode 100644 index 000000000..13aa20acf --- /dev/null +++ b/plugin/social/error.php @@ -0,0 +1,68 @@ + + + + + + + 소셜 로그인 - <?php echo $provider; ?> + + + + + +
    +

    Error :

    + +
    + 10) ){ ?> +

    잠시후에 다시 시도해 주세요.

    + + 홈으로 + +
    +
    + + + + \ No newline at end of file diff --git a/plugin/social/img/loading_icon.gif b/plugin/social/img/loading_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..3fcf2bd780e6ee864860bda4f7003d242fdf2c98 GIT binary patch literal 9197 zcmds+cTiJ%qpnv%LRP2&LMI^!0YXtxQGt!_Bp`$;y-5cF=?EgAXc7Wa1OaK%dv78g zOF)n!x)B5s6akU00(NAxFYf)#zTdg`e0R>wJ$LS$v;JBCt(i5m*7JMb_tn+YQB!vz z17x5K0ANrUi^=MJ*sFO$b766zw7AsW)tx9ud^Yjy)0a=pP0hakzC2t!X{l)zW)|!3 z*K4b5U(UR|7JqH{$*`}tuj*0No!y-~op(Y)LtvaRRt#%+Xjq%7y}r6$T3+hz;r?>! z<<9m_$hi<6J{}7zi%(mhYU^tk<`*8{Ufn zr}sbCLuI}WG|)9#VrrPR?`G2))&!6j14QsK(X`+z2@V&+weis@{t+TbjJ$w=JO+7P z5r6r-w?LX=MpkxiexPl!v5Qtw$u&?ut)70%-P16{5$8uOYAGqBBE1~1uzRe}HD3uU zY-nz)2!84~W))cy!kBEn4>ODG^D$;xjw~mR&AeZoe-oNyd3(#K|J|Ff-#@oYl|W5q zjtCD{-wHBsoFOZ7LFPuAYPrSi1wJ8F&y>2{I#ABF&vbmnJXT18(qwMYfILP^C7gF( zdGDAW_Sv|0EK0~>S54{n6B&|d=3-msH_*a;;TlPzNNUs6Jv0bKjGqlnr=*`X*Q+^6 zwO{R2RBXs9VE3pJgOyHedn?^NlHeBw1+i$~5ed;{o77sI-^OeaeGbYxHu6r%M^T{; zzaM?V@Wq4ZzQ~J_3AkY#1TVI18^qh~Pp4~Bcw}w5p#FdO0RxZHcBsil0n7qa05DYD)0_8xMs4xxS&T#(2=S&<;g#j_OxQ2c$=&645sJ`~qSW|dM734mYL&3E`fyDn_9mku zA>Mnvq#~=@r+PTQnt{f9Nhkw(O+w|j5~1-x!*FPg5?ClEQ41N(0~G6OgPFzMO*5eh zwRvNURr0N7B0wF-yRD%*4p+2BeaL`j+`jZRmI~0del9_g%RkMeyIi>Rm<|+``=bPW z%A3OOez*UQ@&k#4gvv~F8)sKQpk^%3J!hKzo)qzQFAAPol@>EvuY@;rb=Cj)$ibS4Lc zt`h1*`FH-_=)VxP$CZDBC<5_sKJ78)pAogkq&+_EfohLWf5T}HIsf3(lYhgfJ=Fch zr#-6uji~=DIQ<0BpY%!llRjyJy3IDRYO+Ir(x(t{drA+n3swRN?L52YQ_E>$eUHe(Gb^M!)ZU zXS{vF=)%R)z6xedLNFoZ(L=LKLr-&~EMHIe&2JdKzh4pcI_sdu1AJPF`^(mF^9$83 znRbI2pWPlN==U-luyf%@*8~_5=-5p+PkmN50ZhN;GvTvv=s?Q++ngn$nEvJKa!pdq zIQp)*F~2i!+|?R~f_QVNqoi6r2sb=+O7Xnldkb^LLwe`5D&*H%HSFJ2DcLEK@LVH@ zj=G;~8EUk*$h$-&!u+Z(6BgrB-U@mh@2qLgRrhQixm|D~8Y;r1%T)Br_lZG2his-) zq{)~USqyHKY2_X8oO-r$!{ay75yNjP%ivKRaknES zyXi9n5FTJ4aHVV7n3s6UAE(z5?ow1Tq7vB&K;7Z4WB2qUE-Y|r#OOvy_A)*W@Y8<; zdn}yo#Hp}9Br+CX<&C^;lMeVjuEN}C0l(2r@4^5x8h_ZHe{H+S>qWNeq<_u~eYty( zL=JraX-p&pZ4ovTU96c^#99#81YjE!6RN?a@Wi}Hr_oh{!118e=ej~OIt)&%6dl5W z2t%DVGqDVP z$QM%sRY+yg=g0%loI};#1>80Q3RR_!Lo(?{O;PmxnraDlB#sCul_7m;-Ecv76cY}i z1yV>jwPc^_OB!?%Sh8!U5QrfavSHKSHFOx8w{f*Re^vcm&f3J#i*6m>ut)E@rjDBM zgKy_G>XBpH+5WkLGzz8lRS)-v&SJG+-WTWS`&=*mVW$`k;$h00U3hz(KIb)RjZ$Ut zQA4)k3ogygR#-{)!Jdfhk?I7%CLmLoe0mf+oAPb`>*&E`+ZyjpD`HY zd|8Lip=Kn46a@iw=)y?%?7~Wut2IZ=fVs4i=R=ZQFF>VfX)t2k_A=O;iFqJCVehw=5Ju$29MwOv0ZOI|KJ(n!w^ ztM4Fl1jubkiegh;7>IC@&T@nc7v4oi@pE_|(o4dw5|np7@1UiNA&eHHmsif_$4a2{ zy^fu4WI$12g>23AKvCHL{@4Bs<&gWA%JH{v z{Bs`qSIY7K+Bg1G4)%>dl>^OKV52ZZ=wHeK@;g6z6@l7Qj(D+<-{_FAVSjTTuV%_r zz0;A+_J>c(FYR8Y1%`VD>quXYmA>@TGw}F5&j_JPBUqk@1TAT9aT!St85DP(n>0V3 zyRIJ3Unwn??O6$m%L;V~b;^qSxJbil>m};BVR^S(?8Wjx!LECE?mD@kr5+CS*TKxP z9-q#YM~jc|MMm)q3$SncVT!YK)ML&B?z=yh$7H#!np!j_>Li;2Mvh7{yOH;nMAJAYiIr$MPDr&P7tuB5(teMbYS}Z#sZcb261xJd2 zV^2-FO)>_4sYE-$eQpCQlOX_y_nR-OZ=>!;LUHe`ne{5J3a5p+DL_z;@>r^|3Yjoo zosPHZX|mrt2m0qaj=7uB{jDMn5Xm)CVvQ;yEriX4UDq8UQ?`Gs3abLa zt&JxGMvZhSh;PD`jtF#eawbI~nmiRIn(OJzz?pcM+6mVkakRwj(vn|a{dOLhIVv{T z-;aP8g2gb%j&8EP0$gnx@uf};e{7@i(~Y>$O8_$-t_bv8;b7>F$53Zo-&|D|q`RdY z@Qz)e8;IDj9ZXNgT#KTK1W^*n#TU9&dIv3zh9gq^#d= zUDbcc3_lSDM>JD^_#&jhP<)E#P^*F<)T^ehdDTKqcHCSx`>j;vQ-Mtibdj1vZ)wi! z-G5ElcGUZ1Lqw|I-3OuchEA{OFGGinzev2646eO;^W#-o8+Qb|sWDO3(R~I-&P~SQ2edlrPT7fr;z`eZIFWElDavg;?Q&Y6NIiR`m z)xdDPX;qmoZ@Kc*Vd!(3%#Eq^H*={)4r-^3tFO7uBvf#}cw9`uhIbYSi^Pp&b{$;4 z>38biW7q#IJ49H$-Qvka@UOB1iLCBMT1=Na=4&_XI;lLf7*o%&z@a`QSp3NE}ymMOVtvQM?u605-9mRw>`~h zX>IYI4+e2q99p?fj9`v{FK7MQ6F{L-i|6xCKtnk=BHVKP`4U9mR^w0 z3>4b$9wy>$N5ON&{%$oplZGIXt-^3L8q@!HU&`&&!T?o{8=r}Elz%?R?=NorEfA+w z&YvqGicIlNj|ET!G3>HLIk#oQ@&S>w!aVvbh#19V?z~Ap6B#iAfdIQy5D&s)JcYx>JV@*)OsE(oGBMhj z8poAA>k<#mXA_)Py0dVJ3iYJo{i3U3jeOl4sOA!MOnQw8A zN}7@u7OfgJb!?r%r=zh8cGcMu-0gdT;#j@OoXFXd{V(ngVN+I&9}zk|xMJO*fl!h( zcz^!%ht8ztwTA02N5!0Cp*i8igqMq54za_F(`R}=_voMOdzFuRc{p{j<3I!>U+dQwk6gQ^*VfW+lKCI>lYD4dvT`8v#cPTsjssy_;!HE=w zyvh#x=NQ<~NSAxWh>euO?rK`TKQ9DvpKb79es+5(T)^wQ!Kc5H3g|6)got3Sy8@{z`XH0M#bfhLRKaC*dyp zl;SHvCUPcIdeAAB^A5&q2_Ej~8Z(I#$IJndaULif*q z9e!fXz5eW3hn&bxwoGlI1Wl@EPO`r{o^%Cu9-e%zD50Clyo|JIH<1=b5kpVzEENhy zLpS9@Dq#eO?j__U4CEkNnw&CD&cl}#Chi_{KhQsT?b(5OBcVQ`jfIfw8B#Rm=G*Rh zV9E4~Y#cF#-Ae_Kg!0{U;+8jIw`kC7_6Qfp6usDFU*%~EU?;OvJ+~ttc#7HytJk#e zdQ^u+1v^b(G-M@Cdvofe1fj*u`K&Yg+N5|T`odt^b&B7^$vGy#cQh+-c2EirdY__56JLn!s#f zhL(<3X@I&_co_)NlVxvmPJ$BP_sFxdQD&OX(3=2xgMCxWgiMdHn9-Ak+;grhgM85g zp%G}a7g#&A*K9XLOeK(x7=^^cpx@8v0>xvHt29P__O@g-4JE&X9k?5jbbj(|Kc?mk zo$M1jeg5mi5?b~1{MsdW7rU`z!WJLZ>sq8-L>yX`ZGJT4F?bcHC?;2LcCX!|WCZrh ze3Gx-!Mdc6Z{}Yp5*BlF-gOa+~TI&Lz!@Ks0c2_J{sY#n?$C4dGA3wVYhy2SR^&g*? z{@#}Us&D@}PW=bxrT@fj>8~L5S9hC{M4sCu!7%?=juI=_?;?&C?a36cL+h*NCBdIE z1<|Mzp}qdL=4NdD>(-u1MKNiTk+IlN9XvD9FHA&ENP-{+5t6$=-SdiAE~NI}jwFXd z$zhN=Zhv}HQxmsPaqgu8du*}@zZ?$i=|S-)TO)aI4>#|rQ+q>K2=)RJ*8AWA?iFmn zLInGG0o^pAkoWAYkeS}A=^k*?)LQP=+u_>;xs`KkaI~HtfOpjNLj^{Eblxw$FL?P2 z@M)tT3IV4Mjr#PF=vwEvp)BNPKqecFG!%PA;`o&2e=wSROhXB{F#OAX+`66V7Xb-pP5k~{)<&Ws6B&LsZW$g+ zi$#d>OB_CZa+C#Rs8C3`^4eAARaUq5%PjjXRV8ty1btdQiks<7p~sS40fe9cYWPdy z!VyKw+IV8DNzr!Ps<5$k*znH4(g=7RU*?SY@p)Cc{erY$$7!9f>HEA$*N=Ba@;(Z< zx(u9>X(NI57aSu$=K(ztI-lUeP$Zl@8#M_}cJkTz-p32!o3zF;iY*xn5-<1ESPu^HI-T`rPb=Cun54>^-j; zhAx*WHTKt=$$+Iwwx@gARSFym;Yjg(oy?hrD)W3{XmvA9faj(I;E}&#i~K$4u!JEF zHVYsyIsi5IT*RWiueIyJfg;AdPV6;bjD!@EeNZDBE@%?S26?2&446>oAyP=5UK^y7nYV;&`1~q(YTB`gHBc10I9) zCtbxYojEXRsbosIbfDe)>jE-b@$D?W#NMJr@W-3Xz?0b|{o{X}Zxxemc(yU+`b|mX zjAO%H@3Hd_{3u8aOUJfo?f-zJ|8-9Kuh{Jf$~W7v)QEq1Rl(kz#C!b2UbnN2|5tMo z{^y(|{ZDg}^v^jd2#OTglPIUuKer@&hWy{XPl~JbPm5}Yw?Z?dORg6emuuM;85i6Z zZtEDv2>abjVka8^oRyyWx}c%MuiG(qlxD&lXVIaL zjm98UQ6biy8DIg%rTP{PkK+Y*q5Gtwu;$?eiI$6oG}o$jA#0dzm0H%OSi%4$805&K z&ev6`#odxCGefT%>Rz^-50@n`E0&yqz(ZR|NhB%XeNja%OtLt|IP}0AZ}o>Wvx=bV z@`7z9N`Q-u30EKf%Agxz2wszGHni$0i=cBQ$?+je3Z-s|7_F+&v&ZX%FhzlQDkn+= zs@;y;Zb{*h*AbfQr#_E`j-AU zpc|H!L0*B$x|_UJeySH^CwSut;{gXX5`BXx38XP-j7nM zDz&rgf%<*FZE7zIXRqDLlx0EfsdR#Y(P=`4B_TV+5)-j_pSJS zikzTwBgBlZdi5Z(#G|%l+-|6tQ_!H6aa?`zBj~BSZ0Fr|7y5(Ji%0kZVp^_-zPF^T zhvpH8Ua1X;b6U_TmG@8elPWPEy#XZk(rhqWjQk+=*8}4FU`W(@&EYX4w~gjIru>^@ z2hJ30JpH*OQILGZ2(I6p`nf;31-80ojo;r49|%yna{RDh*-einB(DGX)~w5&^{>UT zLYrjdiBlWoH*UC*wqXzR6WYiATK~jw^$~lq@D~+RYh$aRvnGesthHPtjMcStbb_r; x9kx?bKNcLU=C5-&(42bk*jZQe1D;1bY{K1KEVWI&4tP+{95cM28etdkKLD{a7Fz%S literal 0 HcmV?d00001 diff --git a/plugin/social/includes/functions.php b/plugin/social/includes/functions.php new file mode 100644 index 000000000..66efc5286 --- /dev/null +++ b/plugin/social/includes/functions.php @@ -0,0 +1,897 @@ +getSessionData(); + + return $g5['hybrid_auth']->authenticate($provider); +} + +function social_before_join_check($url=''){ + global $g5, $config; + + if( $provider_name = social_get_request_provider() ){ + //재가입 방지 + if( $user_profile = social_session_exists_check() ){ + + $sql = sprintf("select * from {$g5['social_profile_table']} where provider = '%s' and identifier = '%s' ", $provider_name, $user_profile->identifier); + + $is_exist = false; + + $row = sql_fetch($sql); + + if( $row['provider'] ){ + $is_exist = true; + + $time = time() - (86400 * (int) G5_SOCIAL_DELETE_DAY); + + if( empty($row['mb_id']) && ( 0 == G5_SOCIAL_DELETE_DAY || strtotime($row['mp_latest_day']) < $time) ){ + + $sql = "delete from {$g5['social_profile_table']} where mp_no =".$row['mp_no']; + + sql_query($sql); + + $is_exist = false; + } + } + + if( $is_exist ){ + $msg = sprintf("해당 %s ID 로 연결 또는 가입된 내역이 있기 때문에 다시 가입할수 없습니다. 회원이시면 로그인 후 정보 수정에서 계정 연결을 해 주세요.", social_get_provider_service_name($provider_name) ); + + $url = $url ? $url : G5_URL; + alert($msg, $url); + return false; + } + } + + return true; + } + + return false; +} + +function social_get_data($by='provider', $provider, $user_profile){ + global $g5; + + // 소셜 가입이 되어 있는지 체크 + if( $by == 'provider' ){ + + //$sql = sprintf("select * from {$g5['social_member_table']} where sm_service = '%s' and sm_id = '%s' order by mb_id desc ", $provider, $user_profile->sid); + + $sql = sprintf("select * from {$g5['social_profile_table']} where provider = '%s' and identifier = '%s' order by mb_id desc ", $provider, $user_profile->identifier); + + $row = sql_fetch($sql); + + if( !empty($row['mb_id']) ){ + return $row; //mb_id 가 있는 경우에만 데이터를 리턴합니다. + } + + return false; + + } else if ( $by == 'member' ){ // 아이디 또는 이메일이나 별명으로 이미 가입되어 있는지 체크 + + $email = ($user_profile->emailVerified) ? $user_profile->emailVerified : $user_profile->email; + $sid = preg_match("/[^0-9a-z_]+/i", "", $user_profile->sid); + $nick = social_relace_nick($user_profile->displayName); + if( !$nick ){ + $tmp = explode("@", $email); + $nick = $tmp[0]; + } + + $sql = "select mb_nick, mb_email from {$g5['member_table']} where mb_nick = '".$nick."' "; + + if( !empty($email) ){ + $sql .= sprintf(" or mb_email = '%s' ", $email); + } + + $result = sql_query($sql); + + $exists = array(); + + while($row=sql_fetch_array($result)){ + if($row['mb_nick'] && $row['mb_nick'] == $nick){ + $exists['mb_nick'] = $nick; + } + if($row['mb_email'] && $row['mb_email'] == $email){ + $exists['mb_email'] = $email; + } + } + + return $exists; + + } + + return null; +} + +function social_user_profile_replace( $mb_id, $provider, $profile ){ + global $g5; + + if( !$mb_id ) + return; + + //받아온 정보를 암호화 하여 + $object_sha = sha1( serialize( $profile ) ); + + $provider = strtolower($provider); + + $sql = sprintf("SELECT mp_no, mb_id from {$g5['social_profile_table']} where provider= '%s' and identifier = '%s' ", $provider, $profile->identifier); + $result = sql_query($sql); + for($i=0;$row=sql_fetch_array($result);$i++){ //혹시 맞지 않는 데이터가 있으면 삭제합니다. + if( $row['mb_id'] != $mb_id ){ + sql_query(sprintf("DELETE FROM {$g5['social_profile_table']} where mp_no=%d", $row['mp_no'])); + } + } + + $sql = sprintf("SELECT mp_no, object_sha from {$g5['social_profile_table']} where mb_id= '%s' and provider= '%s' and identifier = '%s' ", $mb_id, $provider, $profile->identifier); + + $row = sql_fetch($sql); + + $table_data = array( + "mp_no" => ! empty($row) ? $row['mp_no'] : 'NULL', + 'mb_id' => "'". $mb_id. "'", + 'provider' => "'". $provider . "'", + 'object_sha' => "'". $object_sha . "'", + 'mp_latest_day' => "'". G5_TIME_YMDHIS . "'", + ); + + $fields = array( + 'identifier', + 'profileurl', + 'photourl', + 'displayname', + 'description', + 'gender', + 'age', + 'birthday', + 'birthmonth', + 'birthyear', + ); + + foreach( (array) $profile as $key => $value ){ + $key = strtolower($key); + + if( in_array( $key, $fields ) ) + { + if( in_array($key, array('birthday', 'birthmonth', 'birthyear')) ){ + + $value = $value ? $value : 0; + $table_data[ $key ] = $value; + + } else { + $table_data[ $key ] = "'". (string) $value. "'"; + } + } + } + + $fields = '`' . implode( '`, `', array_keys( $table_data ) ) . '`'; + $values = implode( ", ", array_values( $table_data ) ); + + $sql = "REPLACE INTO {$g5['social_profile_table']} ($fields) VALUES ($values) "; + + sql_query($sql); + + return sql_insert_id(); + +} + +function social_build_provider_config($provider){ + $setting = array( + 'base_url' => G5_SOCIAL_BASE_URL.'/', + 'providers' => array( + $provider => array( + 'enabled' => true, + 'keys' => array( 'id' => null, 'key' => null, 'secret' => null ) + ) + ), + ); + + if( function_exists('social_extends_get_keys') ){ + $setting["providers"][$provider] = social_extends_get_keys($provider); + } + + return $setting; +} + +function social_extends_get_keys($provider){ + + global $config; + + static $r = array(); + + if ( empty($r) ) { + + // Naver + $r['Naver'] = array( + "enabled" => option_array_checked('naver', $config['cf_social_servicelist']) ? true : false, + "keys" => array( + "id" => $config['cf_naver_clientid'], + "secret" => $config['cf_naver_secret'] + ), + ); + + // Kakao + $r['Kakao'] = array( + "enabled" => option_array_checked('kakao', $config['cf_social_servicelist']) ? true : false, + "keys" => array("id" => $config['cf_kakao_rest_key'], "secret" => $config['cf_kakao_rest_key']), + ); + + // Facebook + $r['Facebook'] = array( + "enabled" => option_array_checked('facebook', $config['cf_social_servicelist']) ? true : false, + "keys" => array("id" => $config['cf_facebook_appid'], "secret" => $config['cf_facebook_secret']), + "display" => "popup", + "trustForwarded" => false + ); + + // Google + $r['Google'] = array( + "enabled" => option_array_checked('google', $config['cf_social_servicelist']) ? true : false, + "keys" => array("id" => $config['cf_google_clientid'], + "secret" => $config['cf_google_secret']), + //"access_type" => "offline", // optional + //"approval_prompt" => "force", // optional + ); + + // Twitter + $r['Twitter'] = array( + "enabled" => option_array_checked('twitter', $config['cf_social_servicelist']) ? true : false, + "keys" => array("key" => $config['cf_twitter_key'], "secret" => $config['cf_twitter_secret']), + "trustForwarded" => false + ); + + // Payco + $r['Payco'] = array( + "enabled" => option_array_checked('payco', $config['cf_social_servicelist']) ? true : false, + "keys" => array("id" => $config['cf_payco_clientid'], "secret" => $config['cf_payco_secret']), + "trustForwarded" => false + ); + } + + return $r[$provider]; +} + +function social_escape_request($request){ + return clean_xss_tags( strip_tags($request) ); +} + +function social_get_request_provider(){ + $provider_name = isset($_REQUEST['provider']) ? ucfirst(social_escape_request($_REQUEST['provider'])) : ''; + + return $provider_name; +} + +function social_login_session_clear(){ + $_SESSION["HA::STORE"] = array(); // used by hybridauth library. to clear as soon as the auth process ends. + $_SESSION["HA::CONFIG"] = array(); // used by hybridauth library. to clear as soon as the auth process ends. + set_session('sl_userprofile', ''); + set_session('ss_social_provider', ''); + set_session('social_login_redirect', ''); +} + +function social_session_exists_check(){ + + $provider_name = social_get_request_provider(); + + if(!$provider_name){ + /* + if( isset($_SESSION['sl_userprofile']) ){ + $sl_userprofile = $_SESSION['sl_userprofile']; + reset($sl_userprofile); + $provider_name = key($sl_userprofile); + } + */ + return false; + } + + if( $provider_name && isset($_SESSION['HA::STORE']['hauth_session.'.strtolower($provider_name).'.is_logged_in']) && !empty($_SESSION['sl_userprofile'][$provider_name]) ){ + return json_decode($_SESSION['sl_userprofile'][$provider_name]); + } + + return false; +} + +function social_relace_nick($nick=''){ + + if( empty($nick) ) return ''; + + return preg_replace("/[ #\&\+\-%@=\/\\\:;,\.'\"\^`~\_|\!\?\*$#<>()\[\]\{\}]/i", "", $nick); +} + +function social_get_error_msg($type){ + ob_start(); + + switch( $type ){ + case 0 : echo "지정되지 않은 오류입니다."; break; + case 1 : echo "설정 오류입니다."; break; + case 2 : echo "해당 provider 설정 오류입니다."; break; + case 3 : echo "알수 없거나 비활성화 된 provider 입니다."; break; + case 4 : echo "해당 서비스에 접근할수 있는 권한이 없습니다."; break; + case 5 : echo "인증이 실패되었습니다.. " + . "사용자가 인증을 취소했거나, 공급자가 연결을 거부했습니다."; + break; + case 6 : echo "사용자 프로필 요청이 실패했습니다.사용자가 해당 서비스에 연결되어 있지 않을 경우도 있습니다. " + . "이 경우 다시 인증 요청을 해야 합니다."; + break; + case 7 : echo "사용자가 해당 서비스에 연결되어 있지 않습니다."; + break; + case 8 : echo "해당 서비스가 기능을 지원하지 않습니다."; break; + } + + $get_error = ob_get_clean(); + + return $get_error; +} + +if( !function_exists('replaceQueryParams') ){ + function replaceQueryParams($url, $params) + { + $query = parse_url($url, PHP_URL_QUERY); + parse_str($query, $oldParams); + + if (empty($oldParams)) { + return rtrim($url, '?') . '?' . http_build_query($params); + } + + $params = array_merge($oldParams, $params); + + return preg_replace('#\?.*#', '?' . http_build_query($params), $url); + } +} + +function social_check_login_before(){ + global $is_member, $member; + + $action = isset( $_REQUEST['action'] ) ? social_escape_request($_REQUEST['action']) : ''; + $provider_name = social_get_request_provider(); + $url = isset($_REQUEST['url']) ? $_REQUEST['url'] : G5_URL; + $mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'login'; + $use_popup = G5_SOCIAL_USE_POPUP ? 1 : 2; + $ss_social_provider = get_session('ss_social_provider'); + + if( $provider_name ){ + + try + { + $adapter = social_login_get_provider_adapter( $provider_name ); + + // then grab the user profile + $user_profile = $adapter->getUserProfile(); + + if( ! $is_member ){ + $_SESSION['sl_userprofile'][$provider_name] = json_encode( $user_profile ); + } + } + + catch( Exception $e ) + { + $get_error = social_get_error_msg( $e->getCode() ); + + if( is_object( $adapter ) ){ + $adapter->logout(); + } + + include_once(G5_SOCIAL_LOGIN_PATH.'/error.php'); + exit; + } + + $register_url = G5_BBS_URL.'/register_form.php?provider='.$provider_name; + $register_action_url = G5_BBS_URL.'/register_form_update.php'; + + $login_action_url = G5_HTTPS_BBS_URL."/login_check.php"; + $mylink = (isset($_REQUEST['mylink']) && !empty($_REQUEST['mylink'])) ? 1 : 0; + + //소셜로 이미 가입 했다면 로그인 처리 합니다. + if( $user_provider = social_get_data('provider', $provider_name, $user_profile) ){ + + if( $is_member ){ + $msg = "이미 로그인 하셨거나 잘못된 요청입니다."; + + if( $mylink ){ + $msg = "이미 연결된 아이디가 있거나, 잘못된 요청입니다."; + } + + if( $use_popup == 1 || ! $use_popup ){ //팝업이면 + alert_close( $msg ); + } else { + alert( $msg ); + } + exit; + } + + //데이터가 틀리면 데이터를 갱신 후 로그인 처리 합니다. + + $mb_id = $user_provider['mb_id']; + //이미 소셜로 가입된 데이터가 있다면 password를 필요하지 않으니, 패스워드를 무작위 생성하여 넘깁니다. + $mb_password = sha1( str_shuffle( "0123456789abcdefghijklmnoABCDEFGHIJ" ) ); + + echo social_return_from_provider_page( $provider_name, $login_action_url, $mb_id, $mb_password, $url, $use_popup ); + exit; + + //소셜 데이터와 회원데이터가 일치 하는 경우 계정와 연결할지, 새로 계정을 만들지 선택합니다. + } else { + + if( $is_member && !empty($user_profile) ){ //회원이면 + + if( $mylink ){ + + social_user_profile_replace($member['mb_id'], $provider_name, $user_profile); + + if( is_object( $adapter ) ){ //연결한것은 인증 받은 즉시 로그아웃한다. + $adapter->logout(); + } + + if( $use_popup == 1 || ! $use_popup ){ //팝업이면 + ?> + + $provider_name); + + $url = replaceQueryParams($url, $params); + goto_url($url); + } else { + goto_url(G5_URL); + } + } + exit; + } + + goto_url(G5_URL); + } + + if( !( property_exists($user_profile, 'sid') && !empty($user_profile->sid) ) ){ + $msg = '소셜 데이터 오류'; + if( $use_popup == 1 || ! $use_popup ){ //팝업이면 + alert_close($msg); + } else { + alert($msg); + } + } + + /* + * 회원이 아닌 경우에만 아래 실행 + */ + + $register_url = G5_SOCIAL_LOGIN_URL.'/register_member.php?provider='.$provider_name; + + if( $url ){ + $register_url .= '&url='.urlencode($url); + } + + if( $use_popup == 1 || ! $use_popup ){ //팝업이면 + ?> + + displayName); + $member['mb_sex'] = $user_profile->gender; + $member['mb_email'] = ($user_profile->emailVerified) ? $user_profile->emailVerified : $user_profile->email; + + } + + return $member; +} + +function social_is_login_check(){ + + //소셜 로그인이 맞는지 체크합니다. + if( social_session_exists_check() ){ + return true; + } + + return false; +} + +function social_is_login_password_check($mb_id){ + global $g5; + + $action = isset($_POST['action']) ? $_POST['action'] : ''; + $provider_name = social_get_request_provider(); + + if(!$mb_id || $action === 'link'){ //아이디가 없거나, 계정 연결이면 + if($action === 'link'){ //계정연결이면 같은 서비스명이 있는 경우 + + $sql = sprintf("select count(*) as num from {$g5['social_profile_table']} where provider = '%s' and mb_id = '%s' ", $provider_name, $mb_id); + + $row = sql_fetch($sql); + if( $row['num'] ){ + alert("해당 계정에 이미 $provider_name ID 가 연결되어 있습니다. 연결을 해제 후 다시 시도해 주세요."); + } + } + return false; + } + + //소셜 로그인이 맞는지 체크합니다. + if( $user_profile = social_session_exists_check() ){ + + // db에 이미 쇼셜 계정이 존재하는 경우에는 + if( $user_provider = social_get_data('provider', $provider_name, $user_profile) ){ + + if($user_provider['mb_id'] == $mb_id) + return true; + } + } + + return false; +} + +//소셜 로그인 후 계정 업데이트 +function social_login_success_after($mb, $link='', $mode='', $tmp_create_info=array()){ + global $g5, $config; + + $provider = social_get_request_provider(); + + if( isset($mb['mb_id']) && !empty($mb['mb_id']) && $provider && $user_profile = social_session_exists_check() ){ + + $mb_id = $mb['mb_id']; + //로그인에 성공 했으면 기존 데이터와 비교하여 틀린 값이 없으면 업데이트 합니다. + social_user_profile_replace($mb_id, $provider, $user_profile); + + //소셜로그인의 provider 이름( naver, kakao, facebook 기타 등등 ) 서비스 이름을 세션에 입력합니다. + set_session('ss_social_provider', $provider); + + //소셜로그인 최초 받아온 세션에 저장된 값을 삭제합니다. + if( isset($_SESSION['sl_userprofile']) && isset($_SESSION['sl_userprofile'][$provider]) ){ + unset($_SESSION['sl_userprofile'][$provider]); + } + + if($mode=='register'){ //회원가입 했다면 + + //로그인 처리 한다. + // 회원아이디 세션 생성 + set_session('ss_mb_id', $mb['mb_id']); + // FLASH XSS 공격에 대응하기 위하여 회원의 고유키를 생성해 놓는다. 관리자에서 검사함 - 110106 + set_session('ss_mb_key', md5($mb['mb_datetime'] . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'])); + + if( !empty($user_profile->photoURL) ){ //회원 프로필 사진이 있다면 + $url = $user_profile->photoURL; + + $mb_dir = G5_DATA_PATH."/member_image"; + $mb_dir .= "/".substr($mb_id,0,2); + @mkdir($mb_dir, 0707); + @chmod($mb_dir, 0707); + + $dest_path = "$mb_dir/$mb_id.gif"; + + social_profile_img_resize($dest_path, $user_profile->photoURL, 58, 58 ); + } + + return; + } + + } + + return $link; +} + +function social_profile_img_resize($path, $file_url, $width, $height){ + + list($w, $h, $ext) = @getimagesize($file_url); + if( $w && $h && $ext ){ + $ratio = max($width/$w, $height/$h); + $h = ceil($height / $ratio); + $x = ($w - $width / $ratio) / 2; + $w = ceil($width / $ratio); + + $tmp = imagecreatetruecolor($width, $height); + + if($ext == 1){ + $image = imagecreatefromgif($file_url); + } else if($ext == 3) { + $image = imagecreatefrompng($file_url); + } else { + $image = imagecreatefromjpeg($file_url); + } + imagecopyresampled($tmp, $image, + 0, 0, + $x, 0, + $width, $height, + $w, $h); + + switch ($ext) { + case '2': + imagejpeg($tmp, $path, 100); + break; + case '3': + imagepng($tmp, $path, 0); + break; + case '1': + imagegif($tmp, $path); + break; + } + + /* cleanup memory */ + imagedestroy($image); + imagedestroy($tmp); + } +} + +function social_login_link_account($mb_id, $is_buffer=false, $is_type=''){ + global $g5, $is_admin, $is_guest, $member, $config; + + if( !$mb_id ) + return; + + $sql = "select * from {$g5['social_profile_table']} where mb_id = '".$mb_id."' "; + + $result = sql_query($sql); + + $my_social_accounts = array(); + + for($i=0;$row=sql_fetch_array($result);$i++){ + $my_social_accounts[] = $row; + } + + if( $is_type === 'get_data' ){ + return $my_social_accounts; + } + + ob_start(); + + if( $is_type === 'mb_form' ) { + + global $urlencode; + + static $social_pop_once; + + $my_provides = array(); + + foreach( $my_social_accounts as $account ){ + $my_provides[] = strtolower($account['provider']); + } + + $self_url = G5_BBS_URL."/login.php"; + + //새창을 사용한다면 + if( G5_SOCIAL_USE_POPUP ) + $self_url = G5_SOCIAL_LOGIN_URL.'/popup.php'; + + include(get_social_skin_path().'/social_member_form.skin.php'); + } + + $html = ob_get_clean(); + + if($is_buffer){ + return $html; + } else { + echo $html; + } +} + +function social_get_provider_service_name($provider='', $all=''){ + + $services = array( + 'naver' => '네이버', + 'kakao' => '카카오', + 'daum' => '다음', + 'facebook' => '페이스북', + 'google' => '구글', + 'twitter' => '트위터', + 'payco' => '페이코', + ); + + if( $all ){ + return $services; + } + + $provider = $provider ? strtolower($provider) : ''; + + return ($provider && isset($services[$provider])) ? $services[$provider] : ''; +} + +function social_provider_logout($provider='', $session_delete=1){ + + $provider = $provider ? $provider : get_session('ss_social_provider'); + + if( $provider ){ + + try + { + if( $adapter = social_login_get_provider_adapter( $provider ) ){ + if( $session_delete ) + set_session('ss_social_provider', ''); + + //Hybrid_Auth::logoutAllProviders(); + $adapter->logout(); + } + } + + catch( Exception $e ){ + if( is_object( $adapter ) ){ + $adapter->logout(); + } + } + } +} + +//회원 연결을 해제하거나 회원 탈퇴시 +function social_member_link_delete($mb_id, $mp_no=''){ + + global $g5; + + if(!$mb_id) + return; + + $mp_no = (int) $mp_no; + + if( G5_SOCIAL_DELETE_DAY > 0 ){ + + //mb_id가 없는 소셜 데이터 중에 해당 기간이 넘어간 db 데이터를 삭제합니다. + $time = date("Y-m-d H:i:s", time() - (86400 * (int) G5_SOCIAL_DELETE_DAY)); + + $sql = "delete from {$g5['social_profile_table']} where mb_id = '' and mp_latest_day < '$time' "; + sql_query($sql); + + $sql = "update {$g5['social_profile_table']} set mb_id='', object_sha='', profileurl='', photourl='', displayname='', gender='', age='', birthyear='', birthmonth='', birthday='', mp_latest_day = '".G5_TIME_YMDHIS."' where mb_id= '".$mb_id."'"; + } else { + $sql = "delete from {$g5['social_profile_table']} where mb_id= '".$mb_id."'"; //바로 삭제합니다. + } + + if($mp_no){ + $sql .= " and mp_no=$mp_no"; + } + + sql_query($sql, false); +} + +function social_get_nonce($key=''){ + if( $key == 'd' ){ //nonce_duration + return 7200; + } else if ($key == 'n' ){ //nonce_name + return '_nonce'; + } else { + + if( empty($key) ) + $key = social_get_request_provider(); + + $setting = social_build_provider_config($key); + try{ + return sha1($setting['providers'][$key]['secret']); + } catch(Exception $e) { + return ''; + } + } + + return ''; +} + +function social_nonce_create_query_string( $action = '' , $user = '', $provider = '' ){ + if($nonce_key=social_get_nonce('n')){ + return $nonce_key."=".social_nonce_create( $action , $user, $provider ); + } + return ''; +} + +function social_nonce_create( $action = '' , $user='' , $provider = '' ){ + return substr( social_nonce_generate_hash( $action , $user, $provider ), -12, 10); +} + +function social_nonce_is_valid( $nonce , $action = '' , $user='' , $provider = '' ){ + // Nonce generated 0-12 hours ago + if ( substr(social_nonce_generate_hash( $action , $user, $provider ), -12, 10) == $nonce ){ + return true; + } + return false; +} + +function social_nonce_generate_hash( $action='' , $user='', $provider = '' ){ + $i = ceil( time() / ( social_get_nonce('d') / 2 ) ); + return md5( $i . $action . $user . social_get_nonce($provider) ); +} +?> \ No newline at end of file diff --git a/plugin/social/includes/g5_endpoint.php b/plugin/social/includes/g5_endpoint.php new file mode 100644 index 000000000..29c8f4304 --- /dev/null +++ b/plugin/social/includes/g5_endpoint.php @@ -0,0 +1,32 @@ +dieError( "412 Precondition Failed", $e->getMessage(), $e ); + } + } + + protected function processAuthDone() + { + try { + parent::processAuthDone(); + } + catch( Exception $e ){ + $this->dieError( "410 Gone", $e->getMessage(), $e ); + } + } + + public function dieError( $code, $message, $e ) + { + $get_error = $message; + include_once(G5_SOCIAL_LOGIN_PATH.'/error.php'); + die(); + } +} +?> \ No newline at end of file diff --git a/plugin/social/includes/index.php b/plugin/social/includes/index.php new file mode 100644 index 000000000..e69de29bb diff --git a/plugin/social/includes/loading.php b/plugin/social/includes/loading.php new file mode 100644 index 000000000..aeafe2723 --- /dev/null +++ b/plugin/social/includes/loading.php @@ -0,0 +1,68 @@ + + + + + + + 소셜 로그인 - <?php echo $provider; ?> + + + + + + + + + + + + +

    Loading...


    에 연결중입니다. 잠시만 기다려주세요.
    + + + + +
    + + + + +
    + + + + + \ No newline at end of file diff --git a/plugin/social/includes/providers.php b/plugin/social/includes/providers.php new file mode 100644 index 000000000..ce196085b --- /dev/null +++ b/plugin/social/includes/providers.php @@ -0,0 +1,244 @@ + "Facebook", + "provider_name" => "Facebook", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://developers.facebook.com/apps", + "default_api_scope" => "email, public_profile, user_friends", + + "default_network" => true, + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Google", + "provider_name" => "Google", + "callback" => true, + "require_client_id" => true, + "new_app_link" => "https://console.developers.google.com", + "default_api_scope" => "profile https://www.googleapis.com/auth/plus.profile.emails.read", + + "default_network" => true, + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Twitter", + "provider_name" => "Twitter", + "callback" => true, + "new_app_link" => "https://dev.twitter.com/apps", + + "default_network" => true, + "cat" => "microblogging", + ), + ARRAY( + "provider_id" => "WordPress", + "provider_name" => "WordPress", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://developer.wordpress.com/apps/new/", + + "cat" => "blogging", + ), + ARRAY( + "provider_id" => "Yahoo", + "provider_name" => "Yahoo!", + "new_app_link" => null, + + "cat" => "pleasedie", + ), + ARRAY( + "provider_id" => "LinkedIn", + "provider_name" => "LinkedIn", + "new_app_link" => "https://www.linkedin.com/secure/developer", + + "cat" => "professional", + ), + ARRAY( + "provider_id" => "Disqus", + "provider_name" => "Disqus", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://disqus.com/api/applications/", + + "cat" => "misc", + ), + ARRAY( + "provider_id" => "Instagram", + "provider_name" => "Instagram", + "callback" => true, + "require_client_id" => true, + "new_app_link" => "http://instagr.am/developer/clients/manage/", + + "cat" => "media", + ), + ARRAY( + "provider_id" => "Reddit", + "provider_name" => "Reddit", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://ssl.reddit.com/prefs/apps", + + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Foursquare", + "provider_name" => "Foursquare", + "callback" => true, + "require_client_id" => true, + "new_app_link" => "https://www.foursquare.com/oauth/", + + "cat" => "microblogging", + ), + ARRAY( + "provider_id" => "LastFM", + "provider_name" => "Last.FM", + "new_app_link" => "http://www.lastfm.com/api/account", + + "cat" => "media", + ), + ARRAY( + "provider_id" => "Tumblr", + "provider_name" => "Tumblr", + "new_app_link" => "http://www.tumblr.com/oauth/apps", + + "cat" => "microblogging", // o well + ), + ARRAY( + "provider_id" => "Goodreads", + "provider_name" => "Goodreads", + "callback" => true, + "new_app_link" => "http://www.goodreads.com/api", + + "cat" => "media", + ), + ARRAY( + "provider_id" => "Stackoverflow", + "provider_name" => "Stackoverflow", + "new_app_link" => null, + + "cat" => "programmers", + ), + ARRAY( + "provider_id" => "GitHub", + "provider_name" => "GitHub", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://github.com/settings/applications/new", + "default_api_scope" => "user:email", + + "cat" => "programmers", + ), + ARRAY( + "provider_id" => "Dribbble", + "provider_name" => "Dribbble", + "require_client_id" => true, + "custom_callback" => true, + "new_app_link" => "https://dribbble.com/account/applications/new", + + "cat" => "designers", + ), + ARRAY( + "provider_id" => "500px", + "provider_name" => "px500", + "new_app_link" => "http://developers.500px.com/", + + "cat" => "media", + ), + ARRAY( + "provider_id" => "Skyrock", + "provider_name" => "Skyrock", + "callback" => true, + "new_app_link" => "https://www.skyrock.com/developer/application", + + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Mixi", + "provider_name" => "Mixi", + "new_app_link" => null, + + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Steam", + "provider_name" => "Steam", + "new_app_link" => "http://steamcommunity.com/dev/apikey", + "require_api_key" => true, + + "cat" => "gamers", + ), + ARRAY( + "provider_id" => "TwitchTV", + "provider_name" => "Twitch.tv", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "http://www.twitch.tv/settings?section=applications", + + "cat" => "gamers", + ), + ARRAY( + "provider_id" => "Vkontakte", + "provider_name" => "ВКонтакте", + "callback" => true, + "require_client_id" => true, + "new_app_link" => "http://vk.com/developers.php", + + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "Mailru", + "provider_name" => "Mailru", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "http://api.mail.ru/", + + "cat" => "misc", + ), + ARRAY( + "provider_id" => "Yandex", + "provider_name" => "Yandex", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://oauth.yandex.ru", + + "cat" => "misc", + ), + ARRAY( + "provider_id" => "Odnoklassniki", + "provider_name" => "Odnoklassniki", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "http://dev.odnoklassniki.ru/", + + "cat" => "socialnetworks", + ), + ARRAY( + "provider_id" => "AOL", + "provider_name" => "AOL", + "new_app_link" => null, + + "cat" => "pleasedie", + ), + ARRAY( + "provider_id" => "Live", + "provider_name" => "Windows Live", + "require_client_id" => true, + "new_app_link" => "https://account.live.com/developers/applications/create", + + "cat" => "pleasedie", + ), + ARRAY( + "provider_id" => "PixelPin", + "provider_name" => "PixelPin", + "require_client_id" => true, + "callback" => true, + "new_app_link" => "https://login.pixelpin.co.uk/", + + "cat" => "misc", + ), +); + +?> \ No newline at end of file diff --git a/plugin/social/index.php b/plugin/social/index.php new file mode 100644 index 000000000..6b98177fc --- /dev/null +++ b/plugin/social/index.php @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/plugin/social/register_member.php b/plugin/social/register_member.php new file mode 100644 index 000000000..03c83b58b --- /dev/null +++ b/plugin/social/register_member.php @@ -0,0 +1,48 @@ +displayName); +$user_email = ($user_profile->emailVerified) ? $user_profile->emailVerified : $user_profile->email; +$user_id = $user_profile->sid ? preg_replace("/[^0-9a-z_]+/i", "", $user_profile->sid) : get_social_convert_id($user_profile->identifier, $provider_name); + +$is_exists_id = exist_mb_id($user_id); +$is_exists_name = exist_mb_nick($user_nick, ''); +$is_exists_email = exist_mb_email($user_email, ''); + +// 불법접근을 막도록 토큰생성 +$token = md5(uniqid(rand(), true)); +set_session("ss_token", $token); + +$g5['title'] = '소셜 회원 가입'; + +include_once(G5_BBS_PATH.'/_head.php'); + +$register_action_url = https_url(G5_PLUGIN_DIR.'/'.G5_SOCIAL_LOGIN_DIR, true).'/register_member_update.php'; +$login_action_url = G5_HTTPS_BBS_URL."/login_check.php"; +$req_nick = !isset($member['mb_nick_date']) || (isset($member['mb_nick_date']) && $member['mb_nick_date'] <= date("Y-m-d", G5_SERVER_TIME - ($config['cf_nick_modify'] * 86400))); +$required = ($w=='') ? 'required' : ''; +$readonly = ($w=='u') ? 'readonly' : ''; + +include_once(G5_SOCIAL_SKIN_PATH.'/register_social_member.skin.php'); + +include_once(G5_BBS_PATH.'/_tail.php'); +?> \ No newline at end of file diff --git a/plugin/social/register_member_update.php b/plugin/social/register_member_update.php new file mode 100644 index 000000000..c93e73f0c --- /dev/null +++ b/plugin/social/register_member_update.php @@ -0,0 +1,126 @@ +sid; +$mb_id = trim($_POST['mb_id']); +$mb_password = trim($_POST['mb_password']); +$mb_password_re = trim($_POST['mb_password_re']); +$mb_nick = trim($_POST['mb_nick']); +$mb_email = trim($_POST['mb_email']); +$mb_name = clean_xss_tags(trim($_POST['mb_name'])); +$mb_email = get_email_address($mb_email); + +// 이름, 닉네임에 utf-8 이외의 문자가 포함됐다면 오류 +// 서버환경에 따라 정상적으로 체크되지 않을 수 있음. +$tmp_mb_name = iconv('UTF-8', 'UTF-8//IGNORE', $mb_name); +if($tmp_mb_name != $mb_name) { + alert('이름을 올바르게 입력해 주십시오.'); +} +$tmp_mb_nick = iconv('UTF-8', 'UTF-8//IGNORE', $mb_nick); +if($tmp_mb_nick != $mb_nick) { + alert('닉네임을 올바르게 입력해 주십시오.'); +} + +if ($w == '' && !$mb_password) + alert('비밀번호가 넘어오지 않았습니다.'); +if($w == '' && $mb_password != $mb_password_re) + alert('비밀번호가 일치하지 않습니다.'); + +if ($msg = empty_mb_name($mb_name)) alert($msg, "", true, true); +if ($msg = empty_mb_nick($mb_nick)) alert($msg, "", true, true); +if ($msg = empty_mb_email($mb_email)) alert($msg, "", true, true); +if ($msg = reserve_mb_id($mb_id)) alert($msg, "", true, true); +if ($msg = reserve_mb_nick($mb_nick)) alert($msg, "", true, true); +// 이름에 한글명 체크를 하지 않는다. +//if ($msg = valid_mb_name($mb_name)) alert($msg, "", true, true); +if ($msg = valid_mb_nick($mb_nick)) alert($msg, "", true, true); +if ($msg = valid_mb_email($mb_email)) alert($msg, "", true, true); +if ($msg = prohibit_mb_email($mb_email))alert($msg, "", true, true); + +if ($msg = exist_mb_id($mb_id)) alert($msg); +if ($msg = exist_mb_nick($mb_nick, $mb_id)) alert($msg, "", true, true); +if ($msg = exist_mb_email($mb_email, $mb_id)) alert($msg, "", true, true); + +$data = array( +'mb_id' => $mb_id, +'mb_password' => get_encrypt_string($mb_password), +'mb_nick' => $mb_nick, +'mb_email' => $mb_email, +'mb_name' => $mb_name, +); + +// 회원정보 입력 +$sql = " insert into {$g5['member_table']} + set mb_id = '{$mb_id}', + mb_password = '".get_encrypt_string($mb_password)."', + mb_name = '{$mb_name}', + mb_nick = '{$mb_nick}', + mb_nick_date = '".G5_TIME_YMD."', + mb_email = '{$mb_email}', + mb_email_certify = '".G5_TIME_YMDHIS."', + mb_today_login = '".G5_TIME_YMDHIS."', + mb_datetime = '".G5_TIME_YMDHIS."', + mb_ip = '{$_SERVER['REMOTE_ADDR']}', + mb_level = '{$config['cf_register_level']}', + mb_login_ip = '{$_SERVER['REMOTE_ADDR']}', + mb_mailling = '0', + mb_sms = '0', + mb_open = '0', + mb_open_date = '".G5_TIME_YMD."' "; + +$result = sql_query($sql, false); + +if($result) { + + // 회원가입 포인트 부여 + insert_point($mb_id, $config['cf_register_point'], '회원가입 축하', '@member', $mb_id, '회원가입'); + + // 최고관리자님께 메일 발송 + if ($config['cf_email_mb_super_admin']) { + $subject = '['.$config['cf_title'].'] '.$mb_nick .' 님께서 회원으로 가입하셨습니다.'; + + ob_start(); + include_once (G5_BBS_PATH.'/register_form_update_mail2.php'); + $content = ob_get_contents(); + ob_end_clean(); + + mailer($mb_nick, $mb_email, $config['cf_admin_email'], $subject, $content, 1); + } + + set_session('ss_mb_id', $mb_id); + + set_session('ss_mb_reg', $mb_id); + + //소셜 로그인 계정 추가 + if( function_exists('social_login_success_after') ){ + $mb = get_member($mb_id); + social_login_success_after($mb, '', 'register'); + } + +} + +// 사용자 코드 실행 +@include_once (G5_SOCIAL_SKIN_PATH.'/register_form_update.tail.skin.php'); + +goto_url(G5_HTTP_BBS_URL.'/register_result.php'); +?> \ No newline at end of file diff --git a/plugin/social/unlink.php b/plugin/social/unlink.php new file mode 100644 index 000000000..ac20bc837 --- /dev/null +++ b/plugin/social/unlink.php @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/skin/member/basic/login.skin.php b/skin/member/basic/login.skin.php index 86772c70d..d22e28a83 100644 --- a/skin/member/basic/login.skin.php +++ b/skin/member/basic/login.skin.php @@ -23,6 +23,11 @@ add_stylesheet('', + + @@ -311,15 +298,26 @@ var char_max = parseInt(); // 최대 comment_box('', 'c'); // 댓글 입력폼이 보이도록 처리하기위해서 추가 (root님) - // sns 등록 $(function() { + // sns 등록 $("#bo_vc_send_sns").load( "/view_comment_write.sns.skin.php?bo_table=", function() { save_html = document.getElementById('bo_vc_w').innerHTML; } ); + + + }); + + $(function() { + //댓글열기 + $(".cmt_btn").click(function(){ + $(this).toggleClass("cmt_btn_op"); + $("#bo_vc").toggle(); + }); + }); diff --git a/theme/basic/mobile/skin/board/basic/write.skin.php b/theme/basic/mobile/skin/board/basic/write.skin.php index aa5f5b4ad..ae73aa0b1 100644 --- a/theme/basic/mobile/skin/board/basic/write.skin.php +++ b/theme/basic/mobile/skin/board/basic/write.skin.php @@ -6,8 +6,6 @@ add_stylesheet('', 0 ?>
    -

    -
    @@ -51,115 +49,113 @@ add_stylesheet('', 0 echo $option_hidden; ?> -
    - - - +
    +

    + + +
    + + +
    + + -
    - - - +
    + + +
    - - - - +
    + + class="frm_input full_input " maxlength="20" placeholder="비밀번호"> +
    - - - - +
    + + +
    - - - - +
    + + +
    + +
    + + +
    + +
    + + + +

    이 게시판은 최소 글자 이상, 최대 글자 이하까지 글을 쓰실 수 있습니다.

    + + + + +
    글자
    + +
    + - - - - +
    + 옵션 + +
    - - - - - - - - - - - - - - - - - - - - - + + - - - - +
    +
    + + +
    + + + + + + + + + + +
    - - - - +
    + 자동등록방지 + + +
    - -
    class="frm_input " maxlength="20">
    옵션
    - -
    - - -

    이 게시판은 최소 글자 이상, 최대 글자 이하까지 글을 쓰실 수 있습니다.

    - - - - -
    글자
    - -
    " id="wr_link" class="frm_input wr_link">
    파일 # - - - - - - - -
    자동등록방지 - -
    -
    - - 취소 +
    + 목록 +
    diff --git a/theme/basic/mobile/skin/board/gallery/list.skin.php b/theme/basic/mobile/skin/board/gallery/list.skin.php index 598b13675..44e8f3a73 100644 --- a/theme/basic/mobile/skin/board/gallery/list.skin.php +++ b/theme/basic/mobile/skin/board/gallery/list.skin.php @@ -8,7 +8,13 @@ add_stylesheet('', 0 -

    목록

    + + +
    @@ -22,19 +28,9 @@ add_stylesheet('', 0 -
    -
    - Total 건 - 페이지 -
    - - - - +
    + 전체 건 + 페이지
    @@ -60,47 +56,49 @@ add_stylesheet('', 0
  • - - - - - - 열람중"; - else - echo $list[$i]['num']; - ?> - - + 작성자 프로필이미지 +
    + 조회 + 추천 + 비추천 + 작성일 +
    +
  • +
    게시물이 없습니다."; } ?> @@ -130,29 +130,20 @@ add_stylesheet('', 0
      -
    • 목록
    • +
    • 목록
    • -
    • -
    • -
    • +
    • +
    • +
    - -
    -