-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Push Token Functionality #28
Open
droobah
wants to merge
2
commits into
privacyidea:master
Choose a base branch
from
droobah:pushtoken
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ | |
# Add the possibility to read config from | ||
# /etc/privacyidea/rlm_perl.ini | ||
# 2015-06-10 Cornelius Kölbel <[email protected]> | ||
# Add using of Stripped-User-Name and Realm from the | ||
# Add using of Stripped-User-Name and Realm from the | ||
# RAD_REQUEST | ||
# 2015-04-10 Cornelius Kölbel <[email protected]> | ||
# fix typo in log | ||
|
@@ -29,22 +29,22 @@ | |
# 2014-06-25 Cornelius Kölbel | ||
# changed the used modules from Config::Files to Config::IniFile | ||
# to make it easily run on CentOS with EPEL, without CPAN | ||
# | ||
# | ||
# Copyright (C) 2010 - 2014 LSE Leading Security Experts GmbH | ||
# | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
# | ||
# | ||
# | ||
# Copyright 2002 The FreeRADIUS server project | ||
# Copyright 2002 Boian Jordanov <[email protected]> | ||
|
@@ -64,23 +64,23 @@ | |
|
||
=head1 NAME | ||
|
||
freeradius_perl - Perl module for use with FreeRADIUS rlm_perl, to authenticate against | ||
freeradius_perl - Perl module for use with FreeRADIUS rlm_perl, to authenticate against | ||
LinOTP http://www.linotp.org | ||
privacyIDEA http://www.privacyidea.org | ||
|
||
=head1 SYNOPSIS | ||
|
||
use with freeradius: | ||
use with freeradius: | ||
|
||
Configure rlm_perl to work with privacyIDEA: | ||
in /etc/freeradius/users | ||
in /etc/freeradius/users | ||
set: | ||
DEFAULT Auth-type := perl | ||
|
||
in /etc/freeradius/modules/perl | ||
point | ||
perl { | ||
module = | ||
module = | ||
to this file | ||
|
||
in /etc/freeradius/sites-enabled/<yoursite> | ||
|
@@ -93,33 +93,33 @@ freeradius_perl - Perl module for use with FreeRADIUS rlm_perl, to authenticate | |
|
||
This module enables freeradius to authenticate using privacyIDEA or LinOTP. | ||
|
||
TODO: | ||
TODO: | ||
* checking of server certificate | ||
|
||
|
||
=head2 Methods | ||
|
||
* authenticate | ||
|
||
|
||
=head1 CONFIGURATION | ||
|
||
The authentication request with its URL and default LinOTP/privacyIDEA Realm | ||
The authentication request with its URL and default LinOTP/privacyIDEA Realm | ||
could be defined in a dedicated configuration file, which is expected to be: | ||
|
||
/opt/privacyIDEA/rlm_perl.ini | ||
|
||
This configuration file could contain default definition for URL and REALM like | ||
[Default] | ||
URL = http://192.168.56.1:5001/validate/check | ||
REALM = | ||
REALM = | ||
|
||
But as well could contain "Access-Type" specific configurations, e.g. for the | ||
But as well could contain "Access-Type" specific configurations, e.g. for the | ||
Access-Type 'scope1', this would look like: | ||
|
||
[Default] | ||
URL = https://localhost/validate/check | ||
REALM = | ||
REALM = | ||
CLIENTATTRIBUTE = Calling-Station-Id | ||
|
||
[scope1] | ||
|
@@ -135,7 +135,7 @@ Cornelius Koelbel ([email protected]) | |
|
||
Copyright 2013, 2014 | ||
|
||
This library is free software; you can redistribute it | ||
This library is free software; you can redistribute it | ||
under the GPLv2. | ||
|
||
=head1 SEE ALSO | ||
|
@@ -151,6 +151,8 @@ use Data::Dump; | |
use Try::Tiny; | ||
use JSON; | ||
use Time::HiRes qw( gettimeofday tv_interval ); | ||
use Date::Parse; | ||
use Date::Format; | ||
|
||
|
||
# use ... | ||
|
@@ -177,12 +179,12 @@ use constant RLM_MODULE_NOOP => 7; # /* module succeeded without doing anyt | |
use constant RLM_MODULE_UPDATED => 8; # /* OK (pairs modified) */ | ||
use constant RLM_MODULE_NUMCODES => 9; # /* How many return codes there are */ | ||
|
||
our $ret_hash = { | ||
our $ret_hash = { | ||
0 => "RLM_MODULE_REJECT", | ||
1 => "RLM_MODULE_FAIL", | ||
2 => "RLM_MODULE_OK", | ||
3 => "RLM_MODULE_HANDLED", | ||
4 => "RLM_MODULE_INVALID", | ||
4 => "RLM_MODULE_INVALID", | ||
5 => "RLM_MODULE_USERLOCK", | ||
6 => "RLM_MODULE_NOTFOUND", | ||
7 => "RLM_MODULE_NOOP", | ||
|
@@ -211,6 +213,10 @@ our $cfg_file; | |
|
||
$Config->{FSTAT} = "not found!"; | ||
$Config->{URL} = 'https://127.0.0.1/validate/check'; | ||
$Config->{AUTHURL} = 'https://127.0.0.1/auth'; | ||
$Config->{POLLURL} = 'https://127.0.0.1/token/challenges'; | ||
$Config->{PIUSER} = ''; | ||
$Config->{PIPASS} = ''; | ||
$Config->{REALM} = ''; | ||
$Config->{CLIENTATTRIBUTE} = ''; | ||
$Config->{RESCONF} = ""; | ||
|
@@ -226,14 +232,18 @@ foreach my $file (@CONFIG_FILES) { | |
$CONFIG_FILE = $file; | ||
$Config->{FSTAT} = "found!"; | ||
$Config->{URL} = $cfg_file->val("Default", "URL"); | ||
$Config->{AUTHURL} = $cfg_file->val("Default", "AUTHURL"); | ||
$Config->{POLLURL} = $cfg_file->val("Default", "POLLURL"); | ||
$Config->{PIUSER} = $cfg_file->val("Default", "PIUSER"); | ||
$Config->{PIPASS} = $cfg_file->val("Default", "PIPASS"); | ||
$Config->{REALM} = $cfg_file->val("Default", "REALM"); | ||
$Config->{RESCONF} = $cfg_file->val("Default", "RESCONF"); | ||
$Config->{Debug} = $cfg_file->val("Default", "DEBUG"); | ||
$Config->{SPLIT_NULL_BYTE} = $cfg_file->val("Default", "SPLIT_NULL_BYTE"); | ||
$Config->{SPLIT_NULL_BYTE} = $cfg_file->val("Default", "SPLIT_NULL_BYTE"); | ||
$Config->{SSL_CHECK} = $cfg_file->val("Default", "SSL_CHECK"); | ||
$Config->{TIMEOUT} = $cfg_file->val("Default", "TIMEOUT", 10); | ||
$Config->{CLIENTATTRIBUTE} = $cfg_file->val("Default", "CLIENTATTRIBUTE"); | ||
} | ||
$Config->{CLIENTATTRIBUTE} = $cfg_file->val("Default", "CLIENTATTRIBUTE"); | ||
} | ||
} | ||
|
||
sub mapResponse { | ||
|
@@ -279,8 +289,8 @@ sub mapResponse { | |
my @values = (); | ||
if (ref($attributevalue) eq "") { | ||
&radiusd::radlog(Info, "+++++++ User attribute is a string: $attributevalue"); | ||
push(@values, $attributevalue); | ||
} | ||
push(@values, $attributevalue); | ||
} | ||
if (ref($attributevalue) eq "ARRAY") { | ||
&radiusd::radlog(Info, "+++++++ User attribute is a list: $attributevalue"); | ||
@values = @$attributevalue; | ||
|
@@ -298,28 +308,32 @@ sub mapResponse { | |
} | ||
} | ||
} | ||
|
||
foreach my $key ($cfg_file->Parameters("Mapping")) { | ||
my $radiusAttribute = $cfg_file->val("Mapping", $key); | ||
&radiusd::radlog( Info, "+++ Map: $key -> $radiusAttribute"); | ||
$radReply{$radiusAttribute} = $decoded->{detail}{$key}; | ||
} | ||
|
||
return %radReply; | ||
} | ||
|
||
# Function to handle authenticate | ||
sub authenticate { | ||
|
||
## show where the config comes from - | ||
## show where the config comes from - | ||
# in the module init we can't print this out, so it starts here | ||
&radiusd::radlog( Info, "Config File $CONFIG_FILE ".$Config->{FSTAT} ); | ||
|
||
# we inherrit the defaults | ||
my $URL = $Config->{URL}; | ||
my $AUTHURL = $Config->{AUTHURL}; | ||
my $POLLURL = $Config->{POLLURL}; | ||
my $PIUSER = $Config->{PIUSER}; | ||
my $PIPASS = $Config->{PIPASS}; | ||
my $REALM = $Config->{REALM}; | ||
my $RESCONF = $Config->{RESCONF}; | ||
|
||
my $debug = false; | ||
if ( $Config->{Debug} =~ /true/i ) { | ||
$debug = true; | ||
|
@@ -339,20 +353,31 @@ sub authenticate { | |
my $auth_type = $RAD_CONFIG{"Auth-Type"}; | ||
|
||
try { | ||
&radiusd::radlog( Info, "Looking for config for auth-type $auth_type"); | ||
if ( ( $cfg_file->val( $auth_type, "URL") )) { | ||
&radiusd::radlog( Info, "Looking for config for auth-type $auth_type"); | ||
if ( ( $cfg_file->val( $auth_type, "URL") )) { | ||
$URL = $cfg_file->val( $auth_type, "URL" ); | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "REALM") )) { | ||
if ( ( $cfg_file->val( $auth_type, "AUTHURL") )) { | ||
$AUTHURL = $cfg_file->val( $auth_type, "AUTHURL" ); | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "POLLURL") )) { | ||
$POLLURL = $cfg_file->val( $auth_type, "POLLURL" ); | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "PIUSER") )) { | ||
$PIUSER = $cfg_file->val( $auth_type, "PIUSER" ); | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "PIPASS") )) { | ||
$PIPASS = $cfg_file->val( $auth_type, "PIPASS" ); | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "REALM") )) { | ||
$REALM = $cfg_file->val( $auth_type, "REALM" ); | ||
} | ||
} | ||
if ( ( $cfg_file->val( $auth_type, "RESCONF") )) { | ||
$RESCONF = $cfg_file->val( $auth_type, "RESCONF" ); | ||
} | ||
} | ||
catch { | ||
} catch { | ||
&radiusd::radlog( Info, "Warning: $@" ); | ||
}; | ||
}; | ||
|
||
if ( $debug == true ) { | ||
&log_request_attributes; | ||
|
@@ -401,6 +426,8 @@ sub authenticate { | |
|
||
&radiusd::radlog( Info, "Auth-Type: $auth_type" ); | ||
&radiusd::radlog( Info, "url: $URL" ); | ||
&radiusd::radlog( Info, "pollurl: $POLLURL" ); | ||
&radiusd::radlog( Info, "authurl: $AUTHURL" ); | ||
&radiusd::radlog( Info, "user sent to privacyidea: $params{'user'}" ); | ||
&radiusd::radlog( Info, "realm sent to privacyidea: $params{'realm'}" ); | ||
&radiusd::radlog( Info, "resolver sent to privacyidea: $params{'resConf'}" ); | ||
|
@@ -414,7 +441,7 @@ sub authenticate { | |
&radiusd::radlog( Info, "urlparam $_ \n" ) for ( keys %params ); | ||
} | ||
|
||
my $ua = LWP::UserAgent->new(); | ||
my $ua = LWP::UserAgent->new(); | ||
$ua->env_proxy; | ||
$ua->timeout($timeout); | ||
&radiusd::radlog( Info, "Request timeout: $timeout " ); | ||
|
@@ -453,7 +480,7 @@ sub authenticate { | |
my $message = $decoded->{detail}{message}; | ||
if ( $decoded->{result}{value} ) { | ||
&radiusd::radlog( Info, "privacyIDEA access granted" ); | ||
$RAD_REPLY{'Reply-Message'} = "privacyIDEA access granted"; | ||
$RAD_REPLY{'Reply-Message'} = "privacyIDEA access granted"; | ||
# Add the response hash to the Radius Reply | ||
%RAD_REPLY = ( %RAD_REPLY, mapResponse($decoded)); | ||
$g_return = RLM_MODULE_OK; | ||
|
@@ -462,17 +489,103 @@ sub authenticate { | |
&radiusd::radlog( Info, "privacyIDEA Result status is true!" ); | ||
$RAD_REPLY{'Reply-Message'} = $decoded->{detail}{message}; | ||
if ( $decoded->{detail}{transaction_id} ) { | ||
$RAD_REPLY{'State'} = $decoded->{detail}{transaction_id}; | ||
## we are in challenge response mode: | ||
## 1. split the response in fail, state and challenge | ||
## OTP | ||
## 1. split the response into fail, state and challenge | ||
## 2. show the client the challenge and the state | ||
## 3. get the response and | ||
## 4. submit the response and the state to linotp and | ||
## 4. submit the response and the state to privacyIDEA and | ||
## 5. reply ok or reject | ||
$RAD_REPLY{'State'} = $decoded->{detail}{transaction_id}; | ||
$RAD_CHECK{'Response-Packet-Type'} = "Access-Challenge"; | ||
# Add the response hash to the Radius Reply | ||
%RAD_REPLY = ( %RAD_REPLY, mapResponse($decoded)); | ||
$g_return = RLM_MODULE_HANDLED; | ||
## | ||
## PUSH | ||
## 1. split the response into fail, state and challenge | ||
## 2. poll endpoint for challenge | ||
## 3. reply ok or reject if expired | ||
my $token_type = $decoded->{detail}{type}; | ||
if ($token_type eq "push") { | ||
# perform periodic polling for response | ||
my $token_serial = $decoded->{detail}{serial}; | ||
$RAD_REPLY{'Reply-Message'} = "privacyIDEA token challenge poll failed"; | ||
$g_return = RLM_MODULE_FAIL; | ||
|
||
# retrieve an authorization token from privacyIDEA | ||
%params = (); | ||
$params{"username"} = $PIUSER; | ||
$params{"password"} = $PIPASS; | ||
$starttime = [gettimeofday]; | ||
$response = $ua->post( $AUTHURL, \%params ); | ||
$content = $response->decoded_content(); | ||
$elapsedtime = tv_interval($starttime); | ||
&radiusd::radlog( Info, "elapsed time for privacyidea call: $elapsedtime" ); | ||
if ( $debug == true ) { | ||
&radiusd::radlog( Debug, "Auth Content $content" ); | ||
} | ||
$decoded = $coder->decode($content); | ||
if ($response->is_success && $decoded->{result}{value}) { | ||
my $auth_token = $decoded->{result}{value}{token}; | ||
$ua->default_header('Authorization' => $auth_token); | ||
|
||
# periodic poll for challenges | ||
my $continue_poll = true; | ||
my $challenge_found = false; | ||
do { | ||
sleep(5); | ||
$response = $ua->get("$POLLURL/$token_serial"); | ||
my $content = $response->decoded_content(); | ||
if ($debug == true) { | ||
&radiusd::radlog(Debug, "TokenChallenges Content $content"); | ||
} | ||
$decoded = $coder->decode($content); | ||
if ($response->is_success && $decoded->{result}{status}) { | ||
# check for challenge matching our transaction id | ||
for my $challenge( @{$decoded->{result}{value}{challenges}} ) { | ||
if ($debug == true) { | ||
&radiusd::radlog(Debug, $challenge); | ||
} | ||
|
||
if ($challenge->{"transaction_id"} eq $RAD_REPLY{'State'}) { | ||
$challenge_found = true; | ||
&radiusd::radlog( Info, "challenge object found: ".$challenge->{"challenge"} ); | ||
if ($challenge->{"otp_valid"} == true) { | ||
# user approved request | ||
&radiusd::radlog( Info, "privacyIDEA access granted" ); | ||
$RAD_REPLY{'Reply-Message'} = "privacyIDEA access granted"; | ||
# Add the response hash to the Radius Reply | ||
%RAD_REPLY = ( %RAD_REPLY, mapResponse($decoded)); | ||
$g_return = RLM_MODULE_OK; | ||
$continue_poll = false; | ||
last; | ||
} else { | ||
# check for expiration of request | ||
my $expiry = str2time($challenge->{"expiration"}); | ||
if ($expiry < time()) { | ||
&radiusd::radlog( Info, "privacyIDEA access denied" ); | ||
$RAD_REPLY{'Reply-Message'} = "privacyIDEA push token timed out!"; | ||
$g_return = RLM_MODULE_REJECT; | ||
$continue_poll = false; | ||
last; | ||
} | ||
} | ||
} | ||
} | ||
} else { | ||
# request failed | ||
if ($debug == true) { | ||
&radiusd::radlog(Debug, "privacyIDEA token challenge failed"); | ||
} | ||
last; | ||
} | ||
|
||
} while ($continue_poll && $challenge_found) | ||
|
||
} | ||
} else { | ||
$RAD_CHECK{'Response-Packet-Type'} = "Access-Challenge"; | ||
# Add the response hash to the Radius Reply | ||
%RAD_REPLY = (%RAD_REPLY, mapResponse($decoded)); | ||
$g_return = RLM_MODULE_HANDLED; | ||
} | ||
} else { | ||
&radiusd::radlog( Info, "privacyIDEA access denied" ); | ||
#$RAD_REPLY{'Reply-Message'} = "privacyIDEA access denied"; | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are blocking the RADIUS request in the freeradius server.
So the VPN is still waiting for a RADIUS response from the RADIUS server and the UDP request will soon time out.
How would this be better than having privacyIDEA itself using
push_wait
?