Repository: privacyidea/privacyidea

Issue:
Allow pending certificate requests
When issuing a certificate request to the CA we need to support pending requests.
I.e. the user that runs a ``/token/init?type=certificate`` gets a response that tells him, that the request is taken in review. The user can return to privacyIDEA and fetch the certificate with the serial number.

The CA manager needs to approve the rollout in a certain way, that may depend on the CA type.

This could happen for several reasons. With the local openssl CA this could be, since the CA priv key is password protected and the CA manager needs to manually sign the request.

# Idea about rollout_state

in #2791 we worked on the rollout_state. We will introduce the state `pending`.

Depending on the CA module, the certificate request could be pending. We can start to work on this with the local CA.
The local CA could have a passphrase protected CA key. This way we would need a user interaction to actually enroll the CSR to a certificate.
By doing this we can work on the workflow for pending token enrollment.

We can verify the concept of a pending enrollment by e.g. also require admin interactation when enrolling other tokentype.

# Pending tokens


## Pending after rollout

After the first rollout state the token will be pending. This does not mean that the token is only inactive but that some real cryptographic parts are missing. Like the certitifcate request, where the certificate needs to be manually signed.

Enrolling a certificate successfully returns the API response:

~~~~json

{ "result": { "status": true,
                "value": true },
"detail": { "serial": 1234,
                "pkcs12": "....",
                "certificate": "...." }
}
~~~~

What should a pending certificate return?  Result-value = True makes sense, since the request was processed correctly like privacyIDEA is configured, by taking the CSR under review.

~~~~json
{ "result": { "status": true,
               "value": true },
  "detail": { "message": "Your request has been taken under review.", 
               "serial": 1234 }
}
~~~~

The pending token, the CSR, exists and is assigned to the user. Thus the user can come back, check his tokens and see if later the token will not be pending anymore.

*Do we already have a `message` tag on the top level in detail?*

## State Change from pending to enrolled

How should a token move from state *pending* to the enrolled state?


## Notification

The token owner should be notified somehow, if the token is enrolled and not pending anymore.
Since the state change from *pending* to *enrolled* does not happen via an API call, it is currently not possible to use the normal event handlers.

## Checking and Fetching pending tokens

A normal call to `GET /token` can be used to verify, if a token request is still *pending*.

We should be able list all tokens, that are still pending. This way an admin (or user) can find the 10 pending tokens in a list of 1000 tokens:

    GET /token/?rollout_state=pending


`get_tokens_paginate`  in `lib/tokens.py` already has a parameter `rollout_state`. We only need to add this to the REST API. https://github.com/privacyidea/privacyidea/blob/master/privacyidea/api/token.py#L414


## Backend operations

There is a difference:

* some tokens (like the CSRs) need to be completed on an external system (non-privacyIDEA) and the resulting token (certificate) needs to be transported back to privacyIDEA. The enrolled token needs then to be added to the token DB again. This could be done via a consecutive call to `token/init` either by a human administrator or by a service account. Alternatively we could have an additional endpoint.
* some tokens might be completed **on** privacyIDEA itself by an administrator. The admin would need to view the pending tokens within privacyIDEA to finalize the rollout. The administrator would mark the token to be finalized - maybe be a consecutive call to `token/initi`. Depending on the tokentype privacyIDEA would "know" what to do.

# Implementation considerations

If the API  of `/token/init` returns the same, but only differs in the `detail` response, then we most probably only need to modify the *init_detail* [here](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/api/token.py#L278) in the `tokenobject.get_init_detail`, which fills the `detail response`. 

The `get_init_detail()` can then check, if during the 
`tokenobject.update()` method the token was not finally enrolled. 

In case of the certificate token, this is the call to [caconnector.sign_request](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/tokens/certificatetoken.py#L408) that would not result in a signed certificate but in a pending request, since the CA manager needs to provide e.g. a PIN/Passphrase for signing the request.

## Fetch Pending Certificates

The pending certificates should be able to be fetched with the parameter `rollout_state=pending`.

However, as mentioned in #2966 a `GET /token` on a pending token should also check, if the certificate is still pending and, if it si not, update the token. So if we do a `GET /token/?rollout_state=pending` there would be tokens, that are in the pending state, when the call is issued, but then the during the call the tokens will be marked as not pending anymore. So the question is, should we return those tokens in the call or not?

# Tasks

* [x] Add "rollout_state" as parameter to GET /token/
* [x] Allow tokentype certificates to be pending
* [x] ~~Modify the /token/init repsonse for pending tokens (including the response view in the UI)~~
* [x] ~~Admin can enroll certifiactes manually and thus move tokens from "pending" to "enrolled".~~
* [x] Think about notification when certificates are pending are finally enrolled.


Discussion:
This should also work with an openssl CA, where the private key is password protected.


Resolving patch:
diff --git a/privacyidea/api/lib/postpolicy.py b/privacyidea/api/lib/postpolicy.py
--- a/privacyidea/api/lib/postpolicy.py
+++ b/privacyidea/api/lib/postpolicy.py
@@ -776,7 +776,7 @@ def check_verify_enrollment(request, response):
     """
     This policy decorator is used in the ENROLL scope to
     decorate the /token/init
-    If will check for action=verify_enrollment and ask the user
+    It will check for action=verify_enrollment and ask the user
     in a 2nd step to provide information to verify, that the token was successfully enrolled.
 
     :param request:
diff --git a/privacyidea/api/token.py b/privacyidea/api/token.py
--- a/privacyidea/api/token.py
+++ b/privacyidea/api/token.py
@@ -150,9 +150,9 @@
 @prepolicy(required_piv_attestation, request)
 @prepolicy(verify_enrollment, request)
 @postpolicy(save_pin_change, request)
+@event("token_init", request, g)
 @postpolicy(check_verify_enrollment, request)
 @CheckSubscription(request)
-@event("token_init", request, g)
 @log_with(log, log_entry=False)
 def init():
     """
diff --git a/privacyidea/lib/eventhandler/tokenhandler.py b/privacyidea/lib/eventhandler/tokenhandler.py
--- a/privacyidea/lib/eventhandler/tokenhandler.py
+++ b/privacyidea/lib/eventhandler/tokenhandler.py
@@ -430,7 +430,7 @@ def do(self, action, options=None):
                                         "counter!")
                 else:
                     log.info("Action {0!s} requires serial number. But no serial "
-                             "number could be found in request.")
+                             "number could be found in request {1!s}.".format(action, request))
 
         if action.lower() == ACTION_TYPE.INIT:
             log.info("Initializing new token")


Test patch: