Completed
Push — master ( 20efb0...a2cc06 )
by Hamish
29s
created

BasicAuth::requireLogin()   D

Complexity

Conditions 18
Paths 322

Size

Total Lines 65
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 37
c 1
b 0
f 0
nc 322
nop 3
dl 0
loc 65
rs 4.4006

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Security;
4
5
use SapphireTest;
6
use Director;
7
use SS_HTTPResponse;
8
use SS_HTTPResponse_Exception;
9
use Config;
10
11
/**
12
 * Provides an interface to HTTP basic authentication.
13
 *
14
 * This utility class can be used to secure any request with basic authentication.  To do so,
15
 * {@link BasicAuth::requireLogin()} from your Controller's init() method or action handler method.
16
 *
17
 * It also has a function to protect your entire site.  See {@link BasicAuth::protect_entire_site()}
18
 * for more information. You can control this setting on controller-level by using {@link Controller->basicAuthEnabled}.
19
 *
20
 * @package framework
21
 * @subpackage security
22
 */
23
class BasicAuth {
24
	/**
25
	 * @config
26
	 * @var Boolean Flag set by {@link self::protect_entire_site()}
27
	 */
28
	private static $entire_site_protected = false;
29
30
	/**
31
	 * @config
32
	 * @var String|array Holds a {@link Permission} code that is required
33
	 * when calling {@link protect_site_if_necessary()}. Set this value through
34
	 * {@link protect_entire_site()}.
35
	 */
36
	private static $entire_site_protected_code = 'ADMIN';
37
38
	/**
39
	 * @config
40
	 * @var String Message that shows in the authentication box.
41
	 * Set this value through {@link protect_entire_site()}.
42
	 */
43
	private static $entire_site_protected_message = "SilverStripe test website. Use your CMS login.";
44
45
	/**
46
	 * Require basic authentication.  Will request a username and password if none is given.
47
	 *
48
	 * Used by {@link Controller::init()}.
49
	 *
50
	 * @throws SS_HTTPResponse_Exception
51
	 *
52
	 * @param string $realm
53
	 * @param string|array $permissionCode Optional
54
	 * @param boolean $tryUsingSessionLogin If true, then the method with authenticate against the
55
	 *  session log-in if those credentials are disabled.
56
	 * @return Member $member
57
	 */
58
	public static function requireLogin($realm, $permissionCode = null, $tryUsingSessionLogin = true) {
0 ignored issues
show
Coding Style introduced by
requireLogin uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
59
		$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
60
		if(!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) return true;
61
62
                /*
63
                 * Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache
64
                 * Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or
65
                 * REDIRECT_HTTP_AUTHORIZATION
66
                 *
67
                 * The follow rewrite rule must be in the sites .htaccess file to enable this workaround
68
                 * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
69
                 */
70
		$authHeader = (isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] :
71
			      (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null));
72
		$matches = array();
73
		if ($authHeader &&
74
                        preg_match('/Basic\s+(.*)$/i', $authHeader, $matches)) {
75
			list($name, $password) = explode(':', base64_decode($matches[1]));
76
			$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
77
			$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
78
		}
79
80
		$member = null;
81
		if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
82
			$member = MemberAuthenticator::authenticate(array(
83
				'Email' => $_SERVER['PHP_AUTH_USER'],
84
				'Password' => $_SERVER['PHP_AUTH_PW'],
85
			), null);
86
		}
87
88
		if(!$member && $tryUsingSessionLogin) $member = Member::currentUser();
89
90
		// If we've failed the authentication mechanism, then show the login form
91
		if(!$member) {
92
			$response = new SS_HTTPResponse(null, 401);
93
			$response->addHeader('WWW-Authenticate', "Basic realm=\"$realm\"");
94
95
			if(isset($_SERVER['PHP_AUTH_USER'])) {
96
				$response->setBody(_t('BasicAuth.ERRORNOTREC', "That username / password isn't recognised"));
97
			} else {
98
				$response->setBody(_t('BasicAuth.ENTERINFO', "Please enter a username and password."));
99
			}
100
101
			// Exception is caught by RequestHandler->handleRequest() and will halt further execution
102
			$e = new SS_HTTPResponse_Exception(null, 401);
103
			$e->setResponse($response);
104
			throw $e;
105
		}
106
107
		if($permissionCode && !Permission::checkMember($member->ID, $permissionCode)) {
108
			$response = new SS_HTTPResponse(null, 401);
109
			$response->addHeader('WWW-Authenticate', "Basic realm=\"$realm\"");
110
111
			if(isset($_SERVER['PHP_AUTH_USER'])) {
112
				$response->setBody(_t('BasicAuth.ERRORNOTADMIN', "That user is not an administrator."));
113
			}
114
115
			// Exception is caught by RequestHandler->handleRequest() and will halt further execution
116
			$e = new SS_HTTPResponse_Exception(null, 401);
117
			$e->setResponse($response);
118
			throw $e;
119
		}
120
121
		return $member;
122
	}
123
124
	/**
125
	 * Enable protection of the entire site with basic authentication.
126
	 *
127
	 * This log-in uses the Member database for authentication, but doesn't interfere with the
128
	 * regular log-in form. This can be useful for test sites, where you want to hide the site
129
	 * away from prying eyes, but still be able to test the regular log-in features of the site.
130
	 *
131
	 * If you are including conf/ConfigureFromEnv.php in your _config.php file, you can also enable
132
	 * this feature by adding this line to your _ss_environment.php:
133
	 *
134
	 * define('SS_USE_BASIC_AUTH', true);
135
	 *
136
	 * @param boolean $protect Set this to false to disable protection.
137
	 * @param string $code {@link Permission} code that is required from the user.
138
	 *  Defaults to "ADMIN". Set to NULL to just require a valid login, regardless
139
	 *  of the permission codes a user has.
140
	 * @param string $message
141
	 */
142
	public static function protect_entire_site($protect = true, $code = 'ADMIN', $message = null) {
143
		Config::inst()->update('SilverStripe\\Security\\BasicAuth', 'entire_site_protected', $protect);
144
		Config::inst()->update('SilverStripe\\Security\\BasicAuth', 'entire_site_protected_code', $code);
145
		Config::inst()->update('SilverStripe\\Security\\BasicAuth', 'entire_site_protected_message', $message);
146
	}
147
148
	/**
149
	 * Call {@link BasicAuth::requireLogin()} if {@link BasicAuth::protect_entire_site()} has been called.
150
	 * This is a helper function used by {@link Controller::init()}.
151
	 *
152
	 * If you want to enabled protection (rather than enforcing it),
153
	 * please use {@link protect_entire_site()}.
154
	 */
155
	public static function protect_site_if_necessary() {
156
		$config = Config::inst()->forClass('SilverStripe\\Security\\BasicAuth');
157
		if($config->entire_site_protected) {
158
			self::requireLogin($config->entire_site_protected_message, $config->entire_site_protected_code, false);
159
		}
160
	}
161
162
}
163