Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Add auto-enrollment for user without any token #21

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/privacyidea/pam_python.svg?branch=master)](https://travis-ci.org/privacyidea/pam_python)

This module is to be used with http://pam-python.sourceforge.net/.
It can be used to authenticate with OTP against privacyIDEA. It will also
It can be used to authenticate with OTP against privacyIDEA. It will also
cache future OTP values to enable offline authentication.

To be used like this::
Expand All @@ -10,28 +10,32 @@ To be used like this::

It can take the following parameters:

**url=https://your-server**
**url=https://your-server**

default is https://localhost

**debug**

write debug information to the system log

**realm=yourRealm**

pass additional realm to privacyidea

**nosslverify**

Do not verify the SSL certificate

**prompt=<Prompt>**

The password prompt. Default is "Your OTP".


**api_token=<token>**

The API Token to access admin REST API for auto-enrollment.

**sqlfile=<file>**

This is the SQLite file that is used to store the offline authentication
This is the SQLite file that is used to store the offline authentication
information.
The default file is /etc/privacyidea/pam.sqlite
67 changes: 61 additions & 6 deletions privacyidea_pam.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,15 @@ def __init__(self, pamh, config):
self.sslverify = cacerts
self.realm = config.get("realm")
self.debug = config.get("debug")
self.api_token = config.get("api_token")
self.sqlfile = config.get("sqlfile", "/etc/privacyidea/pam.sqlite")

def make_request(self, data, endpoint="/validate/check"):
def make_request(self, data, endpoint="/validate/check", token=None):
# add a user-agent to be displayed in the Client Application Type
headers = {'user-agent': 'PAM/2.15.0'}
if token:
headers["Authorization"] = token

response = requests.post(self.URL + endpoint, data=data,
headers=headers, verify=self.sslverify)

Expand All @@ -95,6 +99,41 @@ def make_request(self, data, endpoint="/validate/check"):

return json_response

def enroll_user(self, user, pin):
# Generate a new email Token with the provided pin
syslog.syslog(syslog.LOG_DEBUG,
"%s: %s" % (__name__, "Generating a new token"))

data = {"user": self.user,
"genkey": "1",
"pin": pin,
"type": "email",
"dynamic_email": 1}

if self.realm:
data["realm"] = self.realm
json_response = self.make_request(data, endpoint="/token/init", token=self.api_token)

result = json_response.get("result")
detail = json_response.get("detail")

if self.debug:
syslog.syslog(syslog.LOG_DEBUG,
"%s: result: %s" % (__name__, result))
syslog.syslog(syslog.LOG_DEBUG,
"%s: detail: %s" % (__name__, detail))
if result.get("status"):
if result.get("value"):
message = self.pamh.Message(self.pamh.PAM_PROMPT_ECHO_OFF, "Please re-enter your PIN: ")
response = self.pamh.conversation(message)
self.pamh.authtok = response.resp
return self.authenticate(self.pamh.authtok)
else:
syslog.syslog(syslog.LOG_ERR,
"%s: %s" % (__name__,
result.get("error").get("message")))
return self.pamh.PAM_AUTH_ERR

def offline_refill(self, serial, password):

# get refilltoken
Expand Down Expand Up @@ -182,10 +221,10 @@ def authenticate(self, password):
auth_item)
else:
transaction_id = detail.get("transaction_id")
message = detail.get("message").encode("utf-8")

if transaction_id:
attributes = detail.get("attributes") or {}
message = detail.get("message").encode("utf-8")
if "u2fSignRequest" in attributes:
rval = self.u2f_challenge_response(
transaction_id, message,
Expand All @@ -195,11 +234,28 @@ def authenticate(self, password):
message,
attributes)
else:
rval = self.pamh.PAM_AUTH_ERR
if message == 'The user has no tokens assigned':
syslog.syslog(syslog.LOG_DEBUG,
"%s: detail: %s" % (__name__, len(password)))
if len(password)<4:
pam_message = self.pamh.Message(self.pamh.PAM_ERROR_MSG, "You must choose a 4-character minimum PIN.")
self.pamh.conversation(pam_message)
rval = self.pamh.PAM_AUTH_ERR
else:
return self.enroll_user(self.user, password)

else:
syslog.syslog(syslog.LOG_ERR,
"%s: %s" % (__name__, message))
pam_message = self.pamh.Message(self.pamh.PAM_ERROR_MSG, message)
self.pamh.conversation(pam_message)
rval = self.pamh.PAM_AUTH_ERR
else:
error_msg = result.get("error").get("message")
syslog.syslog(syslog.LOG_ERR,
"%s: %s" % (__name__,
result.get("error").get("message")))
"%s: %s" % (__name__, error_msg))
pam_message = self.pamh.Message(self.pamh.PAM_ERROR_MSG, str(error_msg))
self.pamh.conversation(pam_message)

return rval

Expand Down Expand Up @@ -486,4 +542,3 @@ def _create_table(c):
c.execute("CREATE TABLE refilltokens (serial text, refilltoken text)")
except sqlite3.OperationalError:
pass