Skip to content

Commit

Permalink
Item13242: Implement IpRange provider for authenticating based on IP
Browse files Browse the repository at this point in the history
  • Loading branch information
jast committed Aug 25, 2015
1 parent 9e3365a commit b76c230
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 4 deletions.
14 changes: 14 additions & 0 deletions data/System/UnifiedAuthContrib.txt
Expand Up @@ -36,6 +36,20 @@ Auth providers are configured in ={UnifiedAuth}{Providers}=. You can set up the
* =wikiname_format=: how to generate wiki names from the account data. This can use field names as provided by Google (e.g. =name=, =email=, =given_name=, =family_name=). Field names must be prefixed by a =$= sign to be expanded. Any characters not permitted in wiki names will be eliminated after expansion. (Defaults to =$name=)
* =displayname_format=: same, but for the readable display name. (Defaults to =$name=)

---++++ IpRange

Allows automatically logging in a user based on their IP address. This makes it possible to grant restricted, guest-like access based on a user's location.

* =ip_range=: an arrayref or a single value. Each value is either an IP address range (e.g. '192.168.1.0-192.168.1.255') or a netblock in CIDR notation. (Individual IP addresses must be specified as e.g. 1.2.3.4/32 or ::/128.) Both IPv4 and IPv6 are supported.
* =exclude_ip_range=: if set, similar to =ip_range= but prevents these addresses from getting authenticated even if they match one of the IP ranges given in =ip_range=.
* =user_id=: The user ID (of an existing !UnifiedAuth user) which this auth provider authenticates. Anyone logging in from one of the addresses in =ip_range= is automatically assigned this user ID.

If you want to automatically create the user in question, include the following fields, too. Since the user will only be created once, these fields have no effect afterwards.

* =wiki_name=: The WikiName assigned to the user.
* =display_name=: The display name.
* =email=: Optionally, assign an e-mail address.

---++ Installation Instructions

%$INSTALL_INSTRUCTIONS%
Expand Down
11 changes: 10 additions & 1 deletion lib/Foswiki/LoginManager/UnifiedLogin.pm
Expand Up @@ -178,6 +178,7 @@ sub login {
};
if ($@) {
$error = $@;
$error = $@->text if ref($@) && $@->isa("Error");
}
if (ref($loginResult) eq 'HASH' && $loginResult->{user_id}) {
$this->userLoggedIn($loginResult->{user_id});
Expand All @@ -195,7 +196,7 @@ sub login {
my ( $origurl, $origmethod, $origaction ) = _unpackRequest($provider->origin);
my $current_uri = $query->uri;
$current_uri =~ s/\?.*$//;
my ($origurl_noquery) = ($origurl =~ /^(.*)(?:\?.*)?$/);
my ($origurl_noquery) = ($origurl =~ /^(.*?)(?:\?.*)?$/);
if (!$origurl || $origurl_noquery eq $current_uri) {
$origurl = $session->getScriptUrl(0, 'view', $web, $topic);
$session->{request}->delete_all;
Expand Down Expand Up @@ -241,6 +242,14 @@ sub login {
}
}

if (my $forceauthid = $session->{request}->param('uauth_force_provider')) {
if (!exists $Foswiki::cfg{UnifiedAuth}{Providers}{$forceauthid}) {
die "Invalid authentication source requested";
}
my $auth = $this->_authProvider($forceauthid);
return $auth->initiateLogin(_packRequest($session));
}

if (my $authid = $Foswiki::cfg{UnifiedAuth}{DefaultAuthProvider}) {
my $auth = $this->_authProvider($authid);
return $auth->initiateLogin(_packRequest($session));
Expand Down
12 changes: 11 additions & 1 deletion lib/Foswiki/UnifiedAuth.pm
Expand Up @@ -52,7 +52,17 @@ my @schema_updates = (
PRIMARY KEY (group_id, user_id)
)",
"CREATE INDEX group_members_user_id ON group_members (user_id)",
# TODO schema needs more schema
],
[
"ALTER TABLE groups ADD COLUMN mapper_id TEXT",
"ALTER TABLE group_members ADD COLUMN mapper_id TEXT",
"CREATE TABLE group_mappings (
group_id TEXT NOT NULL,
mapper_id TEXT NOT NULL,
mapped_id TEXT NOT NULL,
PRIMARY KEY (group_id, mapper_id, mapped_id),
UNIQUE (mapper_id, mapped_id)
)",
],
);

Expand Down
1 change: 1 addition & 0 deletions lib/Foswiki/UnifiedAuth/Provider.pm
Expand Up @@ -29,6 +29,7 @@ sub initiateLogin {
$cgis->param('uauth_state', $state);
$cgis->param('uauth_provider', $this->{id});
$cgis->flush;
die $cgis->errstr if $cgis->errstr;
return $state;
}

Expand Down
97 changes: 97 additions & 0 deletions lib/Foswiki/UnifiedAuth/Providers/IpRange.pm
@@ -0,0 +1,97 @@
package Foswiki::UnifiedAuth::Providers::IpRange;

use Error;
use JSON;
use Net::CIDR;

use strict;
use warnings;

use Foswiki::Plugins::UnifiedAuthPlugin;
use Foswiki::UnifiedAuth;
use Foswiki::UnifiedAuth::Provider;
our @ISA = qw(Foswiki::UnifiedAuth::Provider);

my @schema_updates = (
[
"CREATE TABLE providers_google (
provider_id TEXT NOT NULL,
email TEXT NOT NULL,
PRIMARY KEY (provider_id, email)
)",
"INSERT INTO meta (type, version) VALUES('providers_google', 0)",
]
);

sub new {
my ($class, $session, $id, $config) = @_;

my $this = $class->SUPER::new($session, $id, $config);

return $this;
}

sub useDefaultLogin {
0;
}

sub initiateLogin {
my ($this, $origin) = @_;

my $state = $this->SUPER::initiateLogin($origin);

my $session = $this->{session};
$session->{response}->redirect(
-url => $this->processUrl() .'?state='. Foswiki::urlEncode($state),
-cookies => $session->{response}->cookies(),
-status => '302',
);
return 1;
}

sub isMyLogin {
my $this = shift;
my $req = $this->{session}{request};
return $req->param('state');
}

sub processLogin {
my $this = shift;
my $req = $this->{session}{request};
my $id = $this->{id};
my $config = $this->{config};
my $state = $req->param('state');
$req->delete('state');
die with Error::Simple("You seem to be using an outdated URL. Please try again.\n") unless $this->SUPER::processLogin($state);

my $iprange = $config->{ip_range};
my @iprange = ref($iprange) ? @$iprange : $iprange;
@iprange = () if @iprange && !defined $iprange[0];
if (!Net::CIDR::cidrlookup($req->remote_addr, @iprange)) {
die with Error::Simple("Address-based login endpoint '$id' failed: your address does not match");
}
my $xiprange = $config->{exclude_ip_range};
my @xiprange = ref($xiprange) ? @$xiprange : $xiprange;
@xiprange = () if @xiprange && !defined $xiprange[0];
if (Net::CIDR::cidrlookup($req->remote_addr, @xiprange)) {
die with Error::Simple("Address-based login endpoint '$id' failed: authentication from your address is not permitted");
}

my $uauth = Foswiki::UnifiedAuth->new();
my $db = $uauth->db;

my $exist = $db->selectrow_array("SELECT COUNT(user_id) FROM users WHERE user_id=?", {}, $config->{user_id});
if (!$exist) {
if (!$config->{wiki_name}) {
die with Error::Simple("Address based login endpoint '$this->{id}' failed: tried to login as a user that does not exist");
}
$uauth->add_user('UTF-8', $this->{id}, $config->{user_id}, $config->{wiki_name}, $config->{display_name}, $config->{email} || '');
}

return {
user_id => $config->{user_id},
data => {}, # probably not needed
};
}

1;
7 changes: 5 additions & 2 deletions templates/uauth.tmpl
Expand Up @@ -19,14 +19,17 @@
%TMPL:DEF{"passwordnote"}%<span class="foswikiUnvisited">[[%SCRIPTURLPATH{view}%/%SYSTEMWEB%/ResetPassword?username=%URLPARAM{usernamestep}%][%MAKETEXT{"I forgot my password"}%]]</span>%TMPL:END%

%TMPL:DEF{"form"}%<div id="foswikiLogin">
<div class="foswikiFormSteps">%TMPL:P{"formstart"}%%TMPL:P{"titlestep"}%
<div class="foswikiFormSteps">%TMPL:P{"errorstep"}%%TMPL:P{"formstart"}%%TMPL:P{"titlestep"}%
%TMPL:P{"usernamestep"}%%TMPL:P{"passwordstep"}%%TMPL:P{"submitstep"}%
%TMPL:P{"formend"}%
%TMPL:P{"seealsostep"}%
</div>
</div>%TMPL:END%

%TMPL:DEF{"auth_providers"}%%RED%In a future version you will get a list of available login providers here.%ENDCOLOR%%TMPL:END%
%TMPL:DEF{"auth_providers"}%
%TMPL:P{"errorstep"}%
%RED%In a future version you will get a list of available login providers here.%ENDCOLOR%
%TMPL:END%

%TMPL:DEF{"link_retry"}%<div id="foswikiLogin">
<div class="foswikiFormSteps">
Expand Down

0 comments on commit b76c230

Please sign in to comment.