Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
F
firewall
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Extensions
firewall
Commits
80cc17e4
Commit
80cc17e4
authored
3 years ago
by
mattwire
Browse files
Options
Downloads
Patches
Plain Diff
Convert firewall to use non-static methods
parent
881d88a5
Branches
Branches containing commit
Tags
Tags containing commit
1 merge request
!7
1.3
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
Civi/Firewall/Firewall.php
+108
-8
108 additions, 8 deletions
Civi/Firewall/Firewall.php
docs/index.md
+11
-9
11 additions, 9 deletions
docs/index.md
docs/releasenotes.md
+4
-0
4 additions, 0 deletions
docs/releasenotes.md
info.xml
+3
-3
3 additions, 3 deletions
info.xml
with
126 additions
and
20 deletions
Civi/Firewall/Firewall.php
+
108
−
8
View file @
80cc17e4
...
...
@@ -10,8 +10,72 @@
*/
namespace
Civi\Firewall
;
use
CRM_Firewall_ExtensionUtil
as
E
;
class
Firewall
{
/**
* The "reason" why a request was blocked or a token was invalid.
*
* @var string
*/
private
$reason
=
''
;
/**
* The user friendly, translateable description for the reason
*
* @var string
*/
private
$reasonDescription
=
''
;
/**
* @return string
*/
public
function
getReason
():
string
{
return
$this
->
reason
;
}
/**
* @param string $reason
*/
private
function
setReason
(
string
$reason
)
{
$this
->
reason
=
$reason
;
switch
(
$reason
)
{
case
'expiredcsrf'
:
$this
->
setReasonDescription
(
E
::
ts
(
'Session expired. Please reload and try again.'
));
break
;
case
'invalidcsrf'
:
case
'tamperedcsrf'
:
// Be careful not to give out too much information that could help someone bypass the CSRF check.
$this
->
setReasonDescription
(
E
::
ts
(
'Session invalid. Please reload and try again.'
));
break
;
case
'blockedfraud'
:
case
'blockedinvalidcsrf'
:
default
:
$this
->
setReasonDescription
(
E
::
ts
(
'Blocked'
));
}
}
/**
* Get the description for the reason
*
* @return string
*/
public
function
getReasonDescription
():
string
{
return
$this
->
reasonDescription
;
}
/**
* Set the description for the reason
*
* @param string $reasonDescription
*/
private
function
setReasonDescription
(
string
$reasonDescription
)
{
$this
->
reasonDescription
=
$reasonDescription
;
}
/**
* The main entry point that is called from hook_civicrm_config (the earliest point we can intercept via extension).
*/
...
...
@@ -29,10 +93,11 @@ class Firewall {
* @return bool
*/
public
function
shouldThisRequestBeBlocked
():
bool
{
$this
->
setReason
(
''
);
// @todo make these settings configurable.
// If there are more than COUNT triggers for this event within time interval then block
$interval
=
'INTERVAL 2 HOUR'
;
$clientIP
=
Firewall
::
getIPAddress
();
$clientIP
=
$this
->
getIPAddress
();
if
(
!
isset
(
$clientIP
))
{
return
FALSE
;
}
...
...
@@ -62,6 +127,7 @@ GROUP BY event_type
case
'FraudEvent'
:
if
(
$dao
->
eventCount
>=
$blockFraudAfter
)
{
$block
=
TRUE
;
$this
->
setReason
(
'blockedfraud'
);
break
2
;
}
break
;
...
...
@@ -69,6 +135,7 @@ GROUP BY event_type
case
'InvalidCSRFEvent'
:
if
(
$dao
->
eventCount
>=
$blockInvalidCSRFAfter
)
{
$block
=
TRUE
;
$this
->
setReason
(
'blockedinvalidcsrf'
);
break
2
;
}
break
;
...
...
@@ -78,11 +145,23 @@ GROUP BY event_type
}
/**
* Generate
and store
a CSRF token. Clients will need to retr
e
ive and pass this into AJAX/API requests.
* Generate a CSRF token. Clients will need to retri
e
ve and pass this into AJAX/API requests.
*
* @return string
* @throws \Exception
*/
public
static
function
getCSRFToken
():
string
{
$firewall
=
new
Firewall
();
return
$firewall
->
generateCSRFToken
();
}
/**
* Generate a CSRF token. Clients will need to retrieve and pass this into AJAX/API requests.
*
* @return string
* @throws \Exception
*/
public
function
generateCSRFToken
():
string
{
$validTo
=
time
()
+
(
int
)
\Civi
::
settings
()
->
get
(
'firewall_csrf_timeout'
);
$random
=
bin2hex
(
random_bytes
(
12
));
$privateKey
=
CIVICRM_SITE_KEY
;
...
...
@@ -90,7 +169,7 @@ GROUP BY event_type
$publicToken
=
"
$validTo
.
$random
."
;
$dataToHash
=
$publicToken
.
$privateKey
;
$dataToHash
.
=
Firewall
::
getIPAddress
();
$dataToHash
.
=
$this
->
getIPAddress
();
// This is the token that we send to the browser, that it must send back.
$publicToken
.
=
hash
(
'sha256'
,
$dataToHash
);
...
...
@@ -105,25 +184,46 @@ GROUP BY event_type
* @return bool
*/
public
static
function
isCSRFTokenValid
(
string
$givenToken
):
bool
{
$firewall
=
new
Firewall
();
return
$firewall
->
checkIsCSRFTokenValid
(
$givenToken
);
}
/**
* Check if the passed in CSRF token is valid and trigger InvalidCSRFEvent if invalid.
*
* @param string $givenToken
*
* @return bool
*/
public
function
checkIsCSRFTokenValid
(
string
$givenToken
):
bool
{
$this
->
setReason
(
''
);
if
(
!
preg_match
(
'/^(\d+)\.([a-f0-9]+)\.([a-f0-9]+)$/'
,
$givenToken
,
$matches
))
{
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
Firewall
::
getIPAddress
(),
'invalid token'
);
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
$this
->
getIPAddress
(),
'invalid token'
);
$this
->
setReason
(
'invalidcsrf'
);
return
FALSE
;
}
if
(
time
()
>
$matches
[
1
])
{
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
Firewall
::
getIPAddress
(),
'expired token'
);
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
$this
->
getIPAddress
(),
'expired token'
);
$this
->
setReason
(
'expiredcsrf'
);
return
FALSE
;
}
$dataToHash
=
"
$matches[1].$matches[2]
."
.
CIVICRM_SITE_KEY
;
$dataToHash
.
=
Firewall
::
getIPAddress
();
$dataToHash
.
=
$this
->
getIPAddress
();
if
(
$matches
[
3
]
!==
hash
(
'sha256'
,
$dataToHash
))
{
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
Firewall
::
getIPAddress
(),
'tampered hash'
);
\Civi\Firewall\Event\InvalidCSRFEvent
::
trigger
(
$this
->
getIPAddress
(),
'tampered hash'
);
$this
->
setReason
(
'tamperedcsrf'
);
return
FALSE
;
}
// OK to continue...
return
TRUE
;
}
public
static
function
getIPAddress
()
{
/**
* Get the IP address of the client. Based on the Drupal function. Support for reverse proxies and whitelists.
*
* @return string
*/
public
function
getIPAddress
():
string
{
if
(
!
isset
(
\Civi
::
$statics
[
__CLASS__
][
'ipAddress'
]))
{
$ipAddress
=
$_SERVER
[
'REMOTE_ADDR'
];
...
...
This diff is collapsed.
Click to expand it.
docs/index.md
+
11
−
9
View file @
80cc17e4
...
...
@@ -2,17 +2,12 @@
This implements a simple firewall for CiviCRM that blocks by IP address in various scenarios.
This is currently a very simple automatic solution with no config and no configuration. It is expected that will change in the future.
## Requirements
*
PHP v7.2+
*
CiviCRM 5.24+
## Installation
See: https://docs.civicrm.org/sysadmin/en/latest/customize/extensions/#installing-a-new-extension
Configure via
**Administer->System Settings->Firewall Settings**
## Usage
## Administration
...
...
@@ -48,11 +43,18 @@ $myVars = [
];
```
OR
```
php
$firewall
=
new
\Civi\Firewall\Firewall
();
$token
=
$firewall
->
generateCSRFToken
();
```
Then in your API/AJAX endpoint check if the token is valid:
```
php
if
(
class_exists
(
'\Civi\Firewall\Firewall'
))
{
if
(
!
\Civi\Firewall\Firewall
::
isCSRFTokenValid
(
CRM_Utils_Request
::
retrieveValue
(
'token'
,
'String'
)))
{
self
::
returnInvalid
();
$firewall
=
new
\Civi\Firewall\Firewall
();
if
(
!
$firewall
->
checkIsCSRFTokenValid
(
CRM_Utils_Request
::
retrieveValue
(
'token'
,
'String'
)))
{
self
::
returnInvalid
(
$firewall
->
getReasonDescription
());
}
}
```
...
...
This diff is collapsed.
Click to expand it.
docs/releasenotes.md
+
4
−
0
View file @
80cc17e4
...
...
@@ -9,6 +9,10 @@ Releases use the following numbering system:
*
**[BC]**
: Items marked with [BC] indicate a breaking change that will require updates to your code if you are using that code in your extension.
## 1.3 (not yet released)
*
Convert Firewall to use non-static methods and provide a "reason" string on failure.
## 1.2.1
*
Fix
[
#16
](
https://lab.civicrm.org/extensions/firewall/-/issues/16
)
Error: Class 'Civi
\\
Firewall
\\
Firewall' not found in...
...
...
This diff is collapsed.
Click to expand it.
info.xml
+
3
−
3
View file @
80cc17e4
...
...
@@ -15,11 +15,11 @@
<url
desc=
"Support"
>
https://mjw.pt/support/firewall
</url>
<url
desc=
"Licensing"
>
http://www.gnu.org/licenses/agpl-3.0.html
</url>
</urls>
<releaseDate>
2021-1
0-1
3
</releaseDate>
<version>
1.
2.1
</version>
<releaseDate>
2021-1
1-0
3
</releaseDate>
<version>
1.
3-dev
</version>
<develStage>
stable
</develStage>
<compatibility>
<ver>
5.
35
</ver>
<ver>
5.
40
</ver>
</compatibility>
<classloader>
<psr4
prefix=
"Civi\"
path=
"Civi"
/>
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment