Merge remote-tracking branch 'origin/master' into identity_oidc
[oweals/gnunet.git] / src / identity-provider / plugin_rest_identity_provider.c
index 6eb85643560958e402344409c93c493e0a3a5e71..1aa1f818deec9951bfa7e799f169e1c032595d08 100644 (file)
  */
 #define GNUNET_REST_API_NS_IDENTITY_CONSUME "/idp/consume"
 
+/**
+ * Authorize namespace
+ */
+#define GNUNET_REST_API_NS_AUTHORIZE "/idp/authorize"
+
+/**
+ * Login namespace
+ */
+#define GNUNET_REST_API_NS_LOGIN "/idp/login"
+
 /**
  * Attribute key
  */
  */
 #define ID_REST_STATE_POST_INIT 1
 
+/**
+ * OIDC response_type key
+ */
+#define OIDC_RESPONSE_TYPE_KEY "response_type"
+
+/**
+ * OIDC client_id key
+ */
+#define OIDC_CLIENT_ID_KEY "client_id"
+
+/**
+ * OIDC scope key
+ */
+#define OIDC_SCOPE_KEY "scope"
+
+/**
+ * OIDC redirect_uri key
+ */
+#define OIDC_REDIRECT_URI_KEY "redirect_uri"
+
+/**
+ * OIDC state key
+ */
+#define OIDC_STATE_KEY "state"
+
+/**
+ * OIDC nonce key
+ */
+#define OIDC_NONCE_KEY "nonce"
+
+/**
+ * OIDC cookie header key
+ */
+#define OIDC_COOKIE_HEADER_KEY "Cookie"
+
+/**
+ * OIDC cookie header information key
+ */
+#define OIDC_COOKIE_HEADER_INFORMATION_KEY "Identity="
+
+/**
+ * OIDC expected response_type while authorizing
+ */
+#define OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE "code"
+
+/**
+ * OIDC expected scope part while authorizing
+ */
+#define OIDC_EXPECTED_AUTHORIZATION_SCOPE "openid"
+
+
+/**
+ * OIDC ignored parameter array
+ */
+char* OIDC_ignored_parameter_array [] =
+{
+  "display",
+  "prompt",
+  "max_age",
+  "ui_locales", 
+  "response_mode",
+  "id_token_hint",
+  "login_hint", 
+  "acr_values"
+};
+
+/**
+ * OIDC authorized identities and times hashmap
+ */
+struct GNUNET_CONTAINER_MultiHashMap *OIDC_authorized_identities;
 
 /**
  * The configuration handle
@@ -235,6 +315,16 @@ struct RequestHandle
    */
   char *emsg;
 
+  /**
+   * Error response uri
+   */
+  char *eredirect;
+
+  /**
+   * Error response description
+   */
+  char *edesc;
+
   /**
    * Reponse code
    */
@@ -308,7 +398,7 @@ do_error (void *cls)
   char *json_error;
 
   GNUNET_asprintf (&json_error,
-                   "{Error while processing request: %s}",
+                   "{error : %s}",
                    handle->emsg);
   resp = GNUNET_REST_create_response (json_error);
   handle->proc (handle->proc_cls, resp, handle->response_code);
@@ -316,6 +406,28 @@ do_error (void *cls)
   GNUNET_free (json_error);
 }
 
+/**
+ * Task run on error, sends error message.  Cleans up everything.
+ *
+ * @param cls the `struct RequestHandle`
+ */
+static void
+do_redirect_error (void *cls)
+{
+  struct RequestHandle *handle = cls;
+  struct MHD_Response *resp;
+  char* redirect;
+  //TODO handle->url is wrong
+  GNUNET_asprintf (&redirect,
+                   "%s?error=%s&error_description=%s",
+                  handle->eredirect, handle->emsg, handle->edesc );
+  resp = GNUNET_REST_create_response ("");
+  MHD_add_response_header (resp, "Location", redirect);
+  handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
+  cleanup_handle (handle);
+  GNUNET_free (redirect);
+}
+
 /**
  * Task run on timeout, sends error message.  Cleans up everything.
  *
@@ -793,10 +905,10 @@ revoke_ticket_cont (struct GNUNET_REST_RequestHandle *con_handle,
                                  strlen (rnd_str),
                                  &ticket.rnd,
                                  sizeof (uint64_t));
-  GNUNET_STRINGS_string_to_data (identity_str,
-                                 strlen (identity_str),
-                                 &ticket.identity,
-                                 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+//  GNUNET_STRINGS_string_to_data (identity_str,
+//                                 strlen (identity_str),
+//                                 &ticket.identity,type filter text
+//                                 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
   GNUNET_STRINGS_string_to_data (audience_str,
                                  strlen (audience_str),
                                  &ticket.audience,
@@ -1012,6 +1124,328 @@ options_cont (struct GNUNET_REST_RequestHandle *con_handle,
   return;
 }
 
+/**
+ * Respond to OPTIONS request
+ *
+ * @param con_handle the connection handle
+ * @param url the url
+ * @param cls the RequestHandle
+ */
+static void
+authorize_cont (struct GNUNET_REST_RequestHandle *con_handle,
+                const char* url,
+                void *cls)
+{
+  struct MHD_Response *resp;
+  struct RequestHandle *handle = cls;
+  char *response_type;
+  char *client_id;
+  char *scope;
+  char *redirect_uri;
+  char *state = NULL;
+  char *nonce = NULL;
+  struct GNUNET_TIME_Absolute current_time, *relog_time;
+  char *login_base_url, *new_redirect;
+  struct GNUNET_HashCode cache_key;
+
+  //TODO clean up method
+
+  /**  The Authorization Server MUST validate all the OAuth 2.0 parameters
+   *   according to the OAuth 2.0 specification.
+   */
+  /**
+   *   If the sub (subject) Claim is requested with a specific value for the
+   *   ID Token, the Authorization Server MUST only send a positive response
+   *   if the End-User identified by that sub value has an active session with
+   *   the Authorization Server or has been Authenticated as a result of the
+   *   request. The Authorization Server MUST NOT reply with an ID Token or
+   *   Access Token for a different user, even if they have an active session
+   *   with the Authorization Server. Such a request can be made either using
+   *   an id_token_hint parameter or by requesting a specific Claim Value as
+   *   described in Section 5.5.1, if the claims parameter is supported by
+   *   the implementation.
+   */
+
+
+
+  // REQUIRED value: client_id
+  GNUNET_CRYPTO_hash (OIDC_CLIENT_ID_KEY, strlen (OIDC_CLIENT_ID_KEY),
+                     &cache_key);
+  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    handle->emsg=GNUNET_strdup("invalid_request");
+    handle->edesc=GNUNET_strdup("Missing parameter: client_id");
+    GNUNET_SCHEDULER_add_now (&do_error, handle);
+    return;
+  }
+  client_id = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                               &cache_key);
+  struct GNUNET_CRYPTO_EcdsaPublicKey pubkey;
+  GNUNET_CRYPTO_ecdsa_public_key_from_string(client_id,
+                                            strlen (client_id),
+                                            &pubkey);
+//  GNUNET_NAMESTORE_zone_to_name();
+  // Checks if client_id is valid:
+  // TODO use GNUNET_NAMESTORE_zone_to_name() function to verify that a delegation to the client_id exists
+  // TODO change check (lookup trusted public_key?)
+//  if( strcmp( client_id, "localhost" ) != 0 )
+//  {
+//    handle->emsg=GNUNET_strdup("unauthorized_client");
+//    handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+//    GNUNET_SCHEDULER_add_now (&do_error, handle);
+//    return;
+//  }
+
+  // REQUIRED value: redirect_uri
+  // TODO verify the redirect uri matches https://<client_id>.zkey[/xyz]
+  GNUNET_CRYPTO_hash (OIDC_REDIRECT_URI_KEY, strlen (OIDC_REDIRECT_URI_KEY),
+                     &cache_key);
+  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    handle->emsg=GNUNET_strdup("invalid_request");
+    handle->edesc=GNUNET_strdup("Missing parameter: redirect_uri");
+    GNUNET_SCHEDULER_add_now (&do_error, handle);
+    return;
+  }
+  redirect_uri = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                               &cache_key);
+
+  // verify the redirect uri matches https://<client_id>.zkey[/xyz]
+  // TODO change check (check client_id->public key == address)
+//  if( strcmp( redirect_uri, "https://localhost:8000" ) != 0 )
+//  {
+//    handle->emsg=GNUNET_strdup("invalid_request");
+//    handle->edesc=GNUNET_strdup("Invalid or mismatching redirect_uri");
+//    GNUNET_SCHEDULER_add_now (&do_error, handle);
+//    return;
+//  }
+  handle->eredirect = GNUNET_strdup(redirect_uri);
+
+  // REQUIRED value: response_type
+  GNUNET_CRYPTO_hash (OIDC_RESPONSE_TYPE_KEY, strlen (OIDC_RESPONSE_TYPE_KEY),
+                     &cache_key);
+  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    handle->emsg=GNUNET_strdup("invalid_request");
+    handle->edesc=GNUNET_strdup("Missing parameter: response_type");
+    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
+    return;
+  }
+  response_type = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                                    &cache_key);
+
+  // REQUIRED value: scope
+  GNUNET_CRYPTO_hash (OIDC_SCOPE_KEY, strlen (OIDC_SCOPE_KEY), &cache_key);
+  if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    handle->emsg=GNUNET_strdup("invalid_request");
+    handle->edesc=GNUNET_strdup("Missing parameter: scope");
+    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
+    return;
+  }
+  scope = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                           &cache_key);
+
+  //RECOMMENDED value: state
+  GNUNET_CRYPTO_hash (OIDC_STATE_KEY, strlen (OIDC_STATE_KEY), &cache_key);
+  if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    state = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                             &cache_key);
+  }
+
+  //OPTIONAL value: nonce
+  GNUNET_CRYPTO_hash (OIDC_NONCE_KEY, strlen (OIDC_NONCE_KEY), &cache_key);
+  if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (handle->rest_handle->url_param_map,
+                                                          &cache_key))
+  {
+    nonce = GNUNET_CONTAINER_multihashmap_get(handle->rest_handle->url_param_map,
+                                             &cache_key);
+  }
+
+  int number_of_ignored_parameter = sizeof(OIDC_ignored_parameter_array) / sizeof(char *);
+  int iterator;
+  for( iterator = 0; iterator < number_of_ignored_parameter; iterator++ )
+  {
+    GNUNET_CRYPTO_hash (OIDC_ignored_parameter_array[iterator],
+                       strlen(OIDC_ignored_parameter_array[iterator]),
+                       &cache_key);
+    if(GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains(handle->rest_handle->url_param_map,
+                                                           &cache_key))
+    {
+      handle->emsg=GNUNET_strdup("access_denied");
+      //TODO rewrite error description
+      handle->edesc=GNUNET_strdup("Server will not handle parameter");
+      GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
+      return;
+    }
+  }
+
+  // Checks if response_type is 'code'
+  if( strcmp( response_type, OIDC_EXPECTED_AUTHORIZATION_RESPONSE_TYPE ) != 0 )
+  {
+    handle->emsg=GNUNET_strdup("unsupported_response_type");
+    handle->edesc=GNUNET_strdup("The authorization server does not support "
+                               "obtaining this authorization code.");
+    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
+    return;
+  }
+  // Checks if scope contains 'openid'
+  if( strstr( scope, OIDC_EXPECTED_AUTHORIZATION_SCOPE ) == NULL )
+  {
+    handle->emsg=GNUNET_strdup("invalid_scope");
+    handle->edesc=GNUNET_strdup("The requested scope is invalid, unknown, or "
+                               "malformed.");
+    GNUNET_SCHEDULER_add_now (&do_redirect_error, handle);
+    return;
+  }
+
+
+  //TODO check other values and use them accordingly
+
+
+  GNUNET_CRYPTO_hash (OIDC_COOKIE_HEADER_KEY, strlen (OIDC_COOKIE_HEADER_KEY),
+                     &cache_key);
+  //No identity-cookie -> redirect to login
+  if ( GNUNET_YES
+      == GNUNET_CONTAINER_multihashmap_contains (con_handle->header_param_map,
+                                                &cache_key) )
+  {
+    //split cookies and find 'Identity' cookie
+    char* cookies = GNUNET_CONTAINER_multihashmap_get (
+       con_handle->header_param_map, &cache_key);
+    char delimiter[] = "; ";
+    char *identity_cookie;
+    identity_cookie = strtok(cookies, delimiter);
+
+    while(identity_cookie != NULL)
+    {
+      if(strstr( identity_cookie, OIDC_COOKIE_HEADER_INFORMATION_KEY ) != NULL)
+      {
+       break;
+      }
+      identity_cookie = strtok(NULL, delimiter);
+    }
+    GNUNET_CRYPTO_hash (identity_cookie, strlen (identity_cookie), &cache_key);
+
+    //No login time for identity -> redirect to login
+    if ( GNUNET_YES
+       == GNUNET_CONTAINER_multihashmap_contains (OIDC_authorized_identities,
+                                                  &cache_key) )
+    {
+      relog_time = GNUNET_CONTAINER_multihashmap_get (
+             OIDC_authorized_identities, &cache_key);
+
+      current_time = GNUNET_TIME_absolute_get();
+
+      GNUNET_CONTAINER_multihashmap_remove_all(OIDC_authorized_identities, &cache_key);
+      // 30 min after old login -> redirect to login
+      if ( current_time.abs_value_us <= relog_time->abs_value_us )
+      {
+       resp = GNUNET_REST_create_response ("");
+       // code = struct GNUNET_IDENTITY_PROVIDER_Ticket
+       GNUNET_IDENTITY_PROVIDER_t
+       MHD_add_response_header (resp, "Location", redirect_uri);
+       handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
+       cleanup_handle (handle);
+       GNUNET_free(relog_time);
+       return;
+      }
+      GNUNET_free(relog_time);
+    }
+  }
+
+
+  // login redirection
+  if ( GNUNET_OK
+      == GNUNET_CONFIGURATION_get_value_string (cfg, "identity-rest-plugin",
+                                               "address", &login_base_url) )
+  {
+    GNUNET_asprintf (&new_redirect, "%s?%s=%s&%s=%s&%s=%s&%s=%s&%s=%s&%s=%s",
+                    login_base_url,
+                    OIDC_RESPONSE_TYPE_KEY,
+                    response_type,
+                    OIDC_CLIENT_ID_KEY,
+                    client_id,
+                    OIDC_REDIRECT_URI_KEY,
+                    redirect_uri,
+                    OIDC_SCOPE_KEY,
+                    scope,
+                    OIDC_STATE_KEY,
+                    (NULL == state) ? state : "",
+                    OIDC_NONCE_KEY,
+                    (NULL == nonce) ? nonce : "");
+    resp = GNUNET_REST_create_response ("");
+    MHD_add_response_header (resp, "Location", new_redirect);
+  }
+  else
+  {
+    handle->emsg = GNUNET_strdup("No server configuration");
+    handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    GNUNET_SCHEDULER_add_now (&do_error, handle);
+    return;
+  }
+  handle->proc (handle->proc_cls, resp, MHD_HTTP_FOUND);
+  cleanup_handle (handle);
+  GNUNET_free(new_redirect);
+  return;
+}
+
+
+/**
+ * Combines an identity with a login time and responds OK to login request
+ *
+ * @param con_handle the connection handle
+ * @param url the url
+ * @param cls the RequestHandle
+ */
+static void
+login_cont (struct GNUNET_REST_RequestHandle *con_handle,
+            const char* url,
+            void *cls)
+{
+
+
+  struct MHD_Response *resp = GNUNET_REST_create_response ("");
+  struct RequestHandle *handle = cls;
+  struct GNUNET_HashCode cache_key;
+  struct GNUNET_TIME_Absolute *current_time;
+  char* cookie;
+  json_t *root;
+  json_error_t error;
+  json_t *identity;
+  root = json_loads (handle->rest_handle->data, 0, &error);
+  identity = json_object_get (root, "identity");
+  if ( json_is_string(identity) )
+  {
+    GNUNET_asprintf (&cookie, "Identity=%s", json_string_value (identity));
+
+    GNUNET_CRYPTO_hash (cookie, strlen (cookie), &cache_key);
+    current_time = GNUNET_new(struct GNUNET_TIME_Absolute);
+    *current_time = GNUNET_TIME_relative_to_absolute (
+       GNUNET_TIME_relative_multiply (GNUNET_TIME_relative_get_minute_ (),
+                                      30));
+    GNUNET_CONTAINER_multihashmap_put (
+       OIDC_authorized_identities, &cache_key, current_time,
+       GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE);
+
+    handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
+  }
+  else
+  {
+    handle->proc (handle->proc_cls, resp, MHD_HTTP_BAD_REQUEST);
+  }
+  GNUNET_free(cookie);
+  json_decref (root);
+  cleanup_handle (handle);
+  return;
+}
+
 /**
  * Handle rest request
  *
@@ -1025,6 +1459,9 @@ init_cont (struct RequestHandle *handle)
     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_IDENTITY_ATTRIBUTES, &list_attribute_cont},
     {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_ATTRIBUTES, &add_attribute_cont},
     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_IDENTITY_TICKETS, &list_tickets_cont},
+    {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_AUTHORIZE, &authorize_cont},
+    {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_LOGIN, &login_cont},
+    {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_AUTHORIZE, &authorize_cont},
     {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_REVOKE, &revoke_ticket_cont},
     {MHD_HTTP_METHOD_POST, GNUNET_REST_API_NS_IDENTITY_CONSUME, &consume_ticket_cont},
     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_IDENTITY_PROVIDER,
@@ -1109,7 +1546,11 @@ rest_identity_process_request(struct GNUNET_REST_RequestHandle *rest_handle,
                               void *proc_cls)
 {
   struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
-
+  if ( NULL == OIDC_authorized_identities )
+  {
+    OIDC_authorized_identities = GNUNET_CONTAINER_multihashmap_create (10,
+                                                                    GNUNET_NO);
+  }
   handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
   handle->proc_cls = proc_cls;
   handle->proc = proc;