>From bae1c770282856cad0ffef67856a2da1adb6c6c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Mon, 9 Feb 2015 12:02:33 +0100
Subject: [PATCH 5/8] IFP: add org.freedesktop.sssd.infopipe.Users.User

Example calls:
dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe /org/freedesktop/sssd/infopipe/Users/LDAP_2ePB/10001 org.freedesktop.DBus.Properties.Get string:org.freedesktop.sssd.infopipe.Users.User string:name
method return sender=:1.159 -> dest=:1.165 reply_serial=2
   variant       string "user-1"

Resolves:
https://fedorahosted.org/sssd/ticket/2150
---
 Makefile.am                                        |   2 +
 src/responder/ifp/ifp_groups.c                     |  39 ++
 src/responder/ifp/ifp_groups.h                     |  37 ++
 src/responder/ifp/ifp_iface.c                      |  14 +
 src/responder/ifp/ifp_iface.xml                    |  15 +
 src/responder/ifp/ifp_iface_generated.c            | 104 +++++
 src/responder/ifp/ifp_iface_generated.h            |  70 +++-
 src/responder/ifp/ifp_private.h                    |  10 +-
 src/responder/ifp/ifp_users.c                      | 451 +++++++++++++++++++++
 src/responder/ifp/ifp_users.h                      |  38 ++
 src/responder/ifp/ifpsrv.c                         |   2 +-
 src/responder/ifp/ifpsrv_util.c                    |  92 ++++-
 .../ifp/org.freedesktop.sssd.infopipe.conf         |   1 +
 src/tests/cmocka/test_ifp.c                        |   2 +-
 14 files changed, 842 insertions(+), 35 deletions(-)
 create mode 100644 src/responder/ifp/ifp_groups.c
 create mode 100644 src/responder/ifp/ifp_groups.h

diff --git a/Makefile.am b/Makefile.am
index 12a7e5a9b882c723a169c6183c7481cb6de4d234..927038c2d62fa1773587179316d101c85f8c8c25 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -543,6 +543,7 @@ dist_noinst_HEADERS = \
     src/responder/ifp/ifp_domains.h \
     src/responder/ifp/ifp_components.h \
     src/responder/ifp/ifp_users.h \
+    src/responder/ifp/ifp_groups.h \
     src/sbus/sbus_client.h \
     src/sbus/sssd_dbus.h \
     src/sbus/sssd_dbus_meta.h \
@@ -1084,6 +1085,7 @@ sssd_ifp_SOURCES = \
     src/responder/ifp/ifp_domains.c \
     src/responder/ifp/ifp_components.c \
     src/responder/ifp/ifp_users.c \
+    src/responder/ifp/ifp_groups.c \
     $(SSSD_UTIL_OBJ) \
     $(SSSD_RESPONDER_OBJ)
 sssd_ifp_CFLAGS = \
diff --git a/src/responder/ifp/ifp_groups.c b/src/responder/ifp/ifp_groups.c
new file mode 100644
index 0000000000000000000000000000000000000000..533e6806df11f4b569fca0e7a78694c3cd576001
--- /dev/null
+++ b/src/responder/ifp/ifp_groups.c
@@ -0,0 +1,39 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    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 3 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/>.
+*/
+
+#include <talloc.h>
+
+#include "db/sysdb.h"
+#include "responder/ifp/ifp_groups.h"
+
+char * ifp_groups_build_path_from_msg(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      struct ldb_message *msg)
+{
+    const char *gid;
+
+    gid = ldb_msg_find_attr_as_string(msg, SYSDB_GIDNUM, NULL);
+
+    if (gid == NULL) {
+        return NULL;
+    }
+
+    return sbus_opath_compose(mem_ctx, IFP_PATH_GROUPS, domain->name, gid);
+}
diff --git a/src/responder/ifp/ifp_groups.h b/src/responder/ifp/ifp_groups.h
new file mode 100644
index 0000000000000000000000000000000000000000..1bdaf3e0c21e5ca53d9839264f87b8d10d3afbf1
--- /dev/null
+++ b/src/responder/ifp/ifp_groups.h
@@ -0,0 +1,37 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    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 3 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/>.
+*/
+
+#ifndef IFP_GROUPS_H_
+#define IFP_GROUPS_H_
+
+#include "responder/ifp/ifp_iface_generated.h"
+#include "responder/ifp/ifp_private.h"
+
+#define IFP_PATH_GROUPS "/org/freedesktop/sssd/infopipe/Groups"
+#define IFP_PATH_GROUPS_TREE IFP_PATH_GROUPS SBUS_SUBTREE_SUFFIX
+
+/* Utility functions */
+
+char * ifp_groups_build_path_from_msg(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      struct ldb_message *msg);
+
+
+#endif /* IFP_GROUPS_H_ */
diff --git a/src/responder/ifp/ifp_iface.c b/src/responder/ifp/ifp_iface.c
index 753bad1269fbecad12cc9e6fea5455cce23aeb8e..1878263c2b4dd20b4d3f5931d56eb39e952bad50 100644
--- a/src/responder/ifp/ifp_iface.c
+++ b/src/responder/ifp/ifp_iface.c
@@ -85,6 +85,19 @@ struct iface_ifp_users iface_ifp_users = {
     .ListByDomainAndName = ifp_users_list_by_domain_and_name
 };
 
+struct iface_ifp_users_user iface_ifp_users_user = {
+    { &iface_ifp_users_user_meta, 0 },
+    .UpdateGroupsList = ifp_users_user_update_groups_list,
+    .get_name = ifp_users_user_get_name,
+    .get_uidNumber = ifp_users_user_get_uid_number,
+    .get_gidNumber = ifp_users_user_get_gid_number,
+    .get_gecos = ifp_users_user_get_gecos,
+    .get_homeDirectory = ifp_users_user_get_home_directory,
+    .get_loginShell = ifp_users_user_get_login_shell,
+    .get_groups = ifp_users_user_get_groups,
+    .get_extraAttributes = ifp_users_user_get_extra_attributes
+};
+
 struct iface_map {
     const char *path;
     struct sbus_vtable *vtable;
@@ -95,6 +108,7 @@ static struct iface_map iface_map[] = {
     { IFP_PATH_DOMAINS_TREE, &iface_ifp_domains.vtable },
     { IFP_PATH_COMPONENTS_TREE, &iface_ifp_components.vtable },
     { IFP_PATH_USERS, &iface_ifp_users.vtable },
+    { IFP_PATH_USERS_TREE, &iface_ifp_users_user.vtable },
     { NULL, NULL },
 };
 
diff --git a/src/responder/ifp/ifp_iface.xml b/src/responder/ifp/ifp_iface.xml
index d0c835949f62c2bad44f24ae251f4a2a273a093d..2c15fddea678b87acc7591442c4e0190da0abaee 100644
--- a/src/responder/ifp/ifp_iface.xml
+++ b/src/responder/ifp/ifp_iface.xml
@@ -125,4 +125,19 @@
             <arg name="result" type="ao" direction="out"/>
         </method>
     </interface>
+
+    <interface name="org.freedesktop.sssd.infopipe.Users.User">
+        <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="iface_ifp_users_user"/>
+
+        <method name="UpdateGroupsList" />
+
+        <property name="name" type="s" access="read" />
+        <property name="uidNumber" type="u" access="read" />
+        <property name="gidNumber" type="u" access="read" />
+        <property name="gecos" type="s" access="read" />
+        <property name="homeDirectory" type="s" access="read" />
+        <property name="loginShell" type="s" access="read" />
+        <property name="groups" type="ao" access="read" />
+        <property name="extraAttributes" type="a{sas}" access="read" />
+    </interface>
 </node>
diff --git a/src/responder/ifp/ifp_iface_generated.c b/src/responder/ifp/ifp_iface_generated.c
index 40a416bc4d45e0600af3767c53fdab4f532c9cd0..1f6c7ba1d1e884dc2be5124b78fc4c775ce17ffe 100644
--- a/src/responder/ifp/ifp_iface_generated.c
+++ b/src/responder/ifp/ifp_iface_generated.c
@@ -652,6 +652,110 @@ const struct sbus_interface_meta iface_ifp_users_meta = {
     sbus_invoke_get_all, /* GetAll invoker */
 };
 
+int iface_ifp_users_user_UpdateGroupsList_finish(struct sbus_request *req)
+{
+   return sbus_request_return_and_finish(req,
+                                         DBUS_TYPE_INVALID);
+}
+
+/* methods for org.freedesktop.sssd.infopipe.Users.User */
+const struct sbus_method_meta iface_ifp_users_user__methods[] = {
+    {
+        "UpdateGroupsList", /* name */
+        NULL, /* no in_args */
+        NULL, /* no out_args */
+        offsetof(struct iface_ifp_users_user, UpdateGroupsList),
+        NULL, /* no invoker */
+    },
+    { NULL, }
+};
+
+/* property info for org.freedesktop.sssd.infopipe.Users.User */
+const struct sbus_property_meta iface_ifp_users_user__properties[] = {
+    {
+        "name", /* name */
+        "s", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_name),
+        sbus_invoke_get_s,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "uidNumber", /* name */
+        "u", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_uidNumber),
+        sbus_invoke_get_u,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "gidNumber", /* name */
+        "u", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_gidNumber),
+        sbus_invoke_get_u,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "gecos", /* name */
+        "s", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_gecos),
+        sbus_invoke_get_s,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "homeDirectory", /* name */
+        "s", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_homeDirectory),
+        sbus_invoke_get_s,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "loginShell", /* name */
+        "s", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_loginShell),
+        sbus_invoke_get_s,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "groups", /* name */
+        "ao", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_groups),
+        sbus_invoke_get_ao,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    {
+        "extraAttributes", /* name */
+        "a{sas}", /* type */
+        SBUS_PROPERTY_READABLE,
+        offsetof(struct iface_ifp_users_user, get_extraAttributes),
+        sbus_invoke_get_aDOsasDE,
+        0, /* not writable */
+        NULL, /* no invoker */
+    },
+    { NULL, }
+};
+
+/* interface info for org.freedesktop.sssd.infopipe.Users.User */
+const struct sbus_interface_meta iface_ifp_users_user_meta = {
+    "org.freedesktop.sssd.infopipe.Users.User", /* name */
+    iface_ifp_users_user__methods,
+    NULL, /* no signals */
+    iface_ifp_users_user__properties,
+    sbus_invoke_get_all, /* GetAll invoker */
+};
+
 /* invokes a handler with a 'ssu' DBus signature */
 static int invoke_ssu_method(struct sbus_request *dbus_req, void *function_ptr)
 {
diff --git a/src/responder/ifp/ifp_iface_generated.h b/src/responder/ifp/ifp_iface_generated.h
index 83ac75b06d4c747cd3a8d45b8f04d08b80e179db..609bd40e6593a3f893774cafbe166e7b7bfd67d0 100644
--- a/src/responder/ifp/ifp_iface_generated.h
+++ b/src/responder/ifp/ifp_iface_generated.h
@@ -61,6 +61,18 @@
 #define IFACE_IFP_USERS_LISTBYNAME "ListByName"
 #define IFACE_IFP_USERS_LISTBYDOMAINANDNAME "ListByDomainAndName"
 
+/* constants for org.freedesktop.sssd.infopipe.Users.User */
+#define IFACE_IFP_USERS_USER "org.freedesktop.sssd.infopipe.Users.User"
+#define IFACE_IFP_USERS_USER_UPDATEGROUPSLIST "UpdateGroupsList"
+#define IFACE_IFP_USERS_USER_NAME "name"
+#define IFACE_IFP_USERS_USER_UIDNUMBER "uidNumber"
+#define IFACE_IFP_USERS_USER_GIDNUMBER "gidNumber"
+#define IFACE_IFP_USERS_USER_GECOS "gecos"
+#define IFACE_IFP_USERS_USER_HOMEDIRECTORY "homeDirectory"
+#define IFACE_IFP_USERS_USER_LOGINSHELL "loginShell"
+#define IFACE_IFP_USERS_USER_GROUPS "groups"
+#define IFACE_IFP_USERS_USER_EXTRAATTRIBUTES "extraAttributes"
+
 /* ------------------------------------------------------------------------
  * DBus handlers
  *
@@ -129,11 +141,11 @@ struct iface_ifp_components {
     int (*Disable)(struct sbus_request *req, void *data);
     int (*ChangeDebugLevel)(struct sbus_request *req, void *data, uint32_t arg_new_level);
     int (*ChangeDebugLevelTemporarily)(struct sbus_request *req, void *data, uint32_t arg_new_level);
-    void (*get_name)(struct sbus_request *, void *data, const char * *);
-    void (*get_debug_level)(struct sbus_request *, void *data, uint32_t *);
-    void (*get_enabled)(struct sbus_request *, void *data, bool *);
-    void (*get_type)(struct sbus_request *, void *data, const char * *);
-    void (*get_providers)(struct sbus_request *, void *data, const char * * *, int *);
+    void (*get_name)(struct sbus_request *, void *data, const char **);
+    void (*get_debug_level)(struct sbus_request *, void *data, uint32_t*);
+    void (*get_enabled)(struct sbus_request *, void *data, bool*);
+    void (*get_type)(struct sbus_request *, void *data, const char **);
+    void (*get_providers)(struct sbus_request *, void *data, const char ***, int *);
 };
 
 /* finish function for Enable */
@@ -151,20 +163,20 @@ int iface_ifp_components_ChangeDebugLevelTemporarily_finish(struct sbus_request
 /* vtable for org.freedesktop.sssd.infopipe.Domains */
 struct iface_ifp_domains {
     struct sbus_vtable vtable; /* derive from sbus_vtable */
-    void (*get_name)(struct sbus_request *, void *data, const char * *);
-    void (*get_provider)(struct sbus_request *, void *data, const char * *);
-    void (*get_primary_servers)(struct sbus_request *, void *data, const char * * *, int *);
-    void (*get_backup_servers)(struct sbus_request *, void *data, const char * * *, int *);
-    void (*get_min_id)(struct sbus_request *, void *data, uint32_t *);
-    void (*get_max_id)(struct sbus_request *, void *data, uint32_t *);
-    void (*get_realm)(struct sbus_request *, void *data, const char * *);
-    void (*get_forest)(struct sbus_request *, void *data, const char * *);
-    void (*get_login_format)(struct sbus_request *, void *data, const char * *);
-    void (*get_fully_qualified_name_format)(struct sbus_request *, void *data, const char * *);
-    void (*get_enumerable)(struct sbus_request *, void *data, bool *);
-    void (*get_use_fully_qualified_names)(struct sbus_request *, void *data, bool *);
-    void (*get_subdomain)(struct sbus_request *, void *data, bool *);
-    void (*get_parent_domain)(struct sbus_request *, void *data, const char * *);
+    void (*get_name)(struct sbus_request *, void *data, const char **);
+    void (*get_provider)(struct sbus_request *, void *data, const char **);
+    void (*get_primary_servers)(struct sbus_request *, void *data, const char ***, int *);
+    void (*get_backup_servers)(struct sbus_request *, void *data, const char ***, int *);
+    void (*get_min_id)(struct sbus_request *, void *data, uint32_t*);
+    void (*get_max_id)(struct sbus_request *, void *data, uint32_t*);
+    void (*get_realm)(struct sbus_request *, void *data, const char **);
+    void (*get_forest)(struct sbus_request *, void *data, const char **);
+    void (*get_login_format)(struct sbus_request *, void *data, const char **);
+    void (*get_fully_qualified_name_format)(struct sbus_request *, void *data, const char **);
+    void (*get_enumerable)(struct sbus_request *, void *data, bool*);
+    void (*get_use_fully_qualified_names)(struct sbus_request *, void *data, bool*);
+    void (*get_subdomain)(struct sbus_request *, void *data, bool*);
+    void (*get_parent_domain)(struct sbus_request *, void *data, const char **);
 };
 
 /* vtable for org.freedesktop.sssd.infopipe.Users */
@@ -188,6 +200,23 @@ int iface_ifp_users_ListByName_finish(struct sbus_request *req, const char *arg_
 /* finish function for ListByDomainAndName */
 int iface_ifp_users_ListByDomainAndName_finish(struct sbus_request *req, const char *arg_result[], int len_result);
 
+/* vtable for org.freedesktop.sssd.infopipe.Users.User */
+struct iface_ifp_users_user {
+    struct sbus_vtable vtable; /* derive from sbus_vtable */
+    int (*UpdateGroupsList)(struct sbus_request *req, void *data);
+    void (*get_name)(struct sbus_request *, void *data, const char **);
+    void (*get_uidNumber)(struct sbus_request *, void *data, uint32_t*);
+    void (*get_gidNumber)(struct sbus_request *, void *data, uint32_t*);
+    void (*get_gecos)(struct sbus_request *, void *data, const char **);
+    void (*get_homeDirectory)(struct sbus_request *, void *data, const char **);
+    void (*get_loginShell)(struct sbus_request *, void *data, const char **);
+    void (*get_groups)(struct sbus_request *, void *data, const char ***, int *);
+    void (*get_extraAttributes)(struct sbus_request *, void *data, hash_table_t **);
+};
+
+/* finish function for UpdateGroupsList */
+int iface_ifp_users_user_UpdateGroupsList_finish(struct sbus_request *req);
+
 /* ------------------------------------------------------------------------
  * DBus Interface Metadata
  *
@@ -210,4 +239,7 @@ extern const struct sbus_interface_meta iface_ifp_domains_meta;
 /* interface info for org.freedesktop.sssd.infopipe.Users */
 extern const struct sbus_interface_meta iface_ifp_users_meta;
 
+/* interface info for org.freedesktop.sssd.infopipe.Users.User */
+extern const struct sbus_interface_meta iface_ifp_users_user_meta;
+
 #endif /* __IFP_IFACE_XML__ */
diff --git a/src/responder/ifp/ifp_private.h b/src/responder/ifp/ifp_private.h
index f76655fa163346a97bac05577ae333309d4daf22..94c9a4b85e2db7fd100dd7ea0b664081d6ba322e 100644
--- a/src/responder/ifp/ifp_private.h
+++ b/src/responder/ifp/ifp_private.h
@@ -73,9 +73,13 @@ int ifp_req_create_handle_failure(struct sbus_request *dbus_req, errno_t err);
 
 errno_t ifp_add_ldb_el_to_dict(DBusMessageIter *iter_dict,
                                struct ldb_message_element *el);
-const char **ifp_parse_attr_list(TALLOC_CTX *mem_ctx, const char *conf_str);
 const char **
-ifp_parse_attr_list_ex(TALLOC_CTX *mem_ctx, const char *conf_str,
-                       const char **defaults);
+ifp_parse_user_attr_list(TALLOC_CTX *mem_ctx, const char *conf_str);
+
+const char **
+ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx);
+
 bool ifp_attr_allowed(const char *whitelist[], const char *attr);
+bool ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr);
+
 #endif /* _IFPSRV_PRIVATE_H_ */
diff --git a/src/responder/ifp/ifp_users.c b/src/responder/ifp/ifp_users.c
index 04fabf77b9cbb49b4ebd67571f40857b8a3b9c00..d9e137d6b34a85328ba0ab2e24f5a5c2fd901799 100644
--- a/src/responder/ifp/ifp_users.c
+++ b/src/responder/ifp/ifp_users.c
@@ -24,10 +24,12 @@
 
 #include "db/sysdb.h"
 #include "util/util.h"
+#include "util/strtonum.h"
 #include "sbus/sssd_dbus_errors.h"
 #include "responder/common/responder.h"
 #include "responder/common/responder_cache_req.h"
 #include "responder/ifp/ifp_users.h"
+#include "responder/ifp/ifp_groups.h"
 
 char * ifp_users_build_path_from_msg(TALLOC_CTX *mem_ctx,
                                      struct sss_domain_info *domain,
@@ -44,6 +46,41 @@ char * ifp_users_build_path_from_msg(TALLOC_CTX *mem_ctx,
     return sbus_opath_compose(mem_ctx, IFP_PATH_USERS, domain->name, uid);
 }
 
+static errno_t ifp_users_decompose_path(struct sss_domain_info *domains,
+                                        const char *path,
+                                        struct sss_domain_info **_domain,
+                                        uid_t *_uid)
+{
+    char **parts = NULL;
+    struct sss_domain_info *domain;
+    uid_t uid;
+    errno_t ret;
+
+    ret = sbus_opath_decompose_exact(NULL, path, IFP_PATH_USERS, 2, &parts);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    domain = find_domain_by_name(domains, parts[0], false);
+    if (domain == NULL) {
+        ret = ERR_DOMAIN_NOT_FOUND;
+        goto done;
+    }
+
+    uid = strtouint32(parts[1], NULL, 10);
+    if (errno != 0) {
+        ret = errno;
+        goto done;
+    }
+
+    *_domain = domain;
+    *_uid = uid;
+
+done:
+    talloc_free(parts);
+    return ret;
+}
+
 static void ifp_users_find_by_name_done(struct tevent_req *req);
 
 int ifp_users_find_by_name(struct sbus_request *sbus_req,
@@ -200,3 +237,417 @@ int ifp_users_list_by_domain_and_name(struct sbus_request *sbus_req,
 {
     return EOK;
 }
+
+static errno_t
+ifp_users_user_get(struct sbus_request *sbus_req,
+                   struct ifp_ctx *ifp_ctx,
+                   uid_t *_uid,
+                   struct sss_domain_info **_domain,
+                   struct ldb_message **_user)
+{
+    struct sss_domain_info *domain;
+    struct ldb_result *res;
+    uid_t uid;
+    errno_t ret;
+
+    ret = ifp_users_decompose_path(ifp_ctx->rctx->domains, sbus_req->path,
+                                   &domain, &uid);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to decompose object path"
+              "[%s] [%d]: %s\n", sbus_req->path, ret, sss_strerror(ret));
+        return ret;
+    }
+
+    if (_user != NULL) {
+        ret = sysdb_getpwuid_with_views(sbus_req, domain, uid, &res);
+        if (ret == EOK && res->count == 0) {
+            *_user = NULL;
+            ret = ENOENT;
+        }
+
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user %u@%s [%d]: %s\n",
+                  uid, domain->name, ret, sss_strerror(ret));
+        } else {
+            *_user = res->msgs[0];
+        }
+    }
+
+    if (ret == EOK || ret == ENOENT) {
+        if (_uid != NULL) {
+            *_uid = uid;
+        }
+
+        if (_domain != NULL) {
+            *_domain = domain;
+        }
+    }
+
+    return ret;
+}
+
+static void ifp_users_get_as_string(struct sbus_request *sbus_req,
+                                    void *data,
+                                    const char *attr,
+                                    const char **_out)
+{
+    struct ifp_ctx *ifp_ctx;
+    struct ldb_message *msg;
+    struct sss_domain_info *domain;
+    errno_t ret;
+
+    *_out = NULL;
+
+    ifp_ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ifp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return;
+    }
+
+    if (!ifp_is_user_attr_allowed(ifp_ctx, attr)) {
+        DEBUG(SSSDBG_TRACE_ALL, "Attribute %s is not allowed\n", attr);
+        return;
+    }
+
+    ret = ifp_users_user_get(sbus_req, ifp_ctx, NULL, &domain, &msg);
+    if (ret != EOK) {
+        return;
+    }
+
+    *_out = sss_view_ldb_msg_find_attr_as_string(domain, msg, attr, NULL);
+
+    return;
+}
+
+static void ifp_users_get_as_uint32(struct sbus_request *sbus_req,
+                                    void *data,
+                                    const char *attr,
+                                    uint32_t *_out)
+{
+    struct ifp_ctx *ifp_ctx;
+    struct ldb_message *msg;
+    struct sss_domain_info *domain;
+    errno_t ret;
+
+    *_out = 0;
+
+    ifp_ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ifp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return;
+    }
+
+    if (!ifp_is_user_attr_allowed(ifp_ctx, attr)) {
+        DEBUG(SSSDBG_TRACE_ALL, "Attribute %s is not allowed\n", attr);
+        return;
+    }
+
+    ret = ifp_users_user_get(sbus_req, ifp_ctx, NULL, &domain, &msg);
+    if (ret != EOK) {
+        return;
+    }
+
+    *_out = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, attr, 0);
+
+    return;
+}
+
+static void ifp_users_user_update_groups_list_done(struct tevent_req *req);
+
+int ifp_users_user_update_groups_list(struct sbus_request *sbus_req,
+                                      void *data)
+{
+    struct tevent_req *req;
+    struct ifp_ctx *ctx;
+    struct sss_domain_info *domain;
+    const char *username;
+    struct ldb_message *user;
+    errno_t ret;
+
+    ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return ERR_INTERNAL;
+    }
+
+    ret = ifp_users_user_get(sbus_req, data, NULL, &domain, &user);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    username = ldb_msg_find_attr_as_string(user, SYSDB_NAME, NULL);
+    if (username == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "User name is empty!\n");
+        return ERR_INTERNAL;
+    }
+
+    req = cache_req_initgr_by_name_send(sbus_req, ctx->rctx->ev, ctx->rctx,
+                                        ctx->ncache, ctx->neg_timeout, 0,
+                                        domain->name, username);
+    if (req == NULL) {
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(req, ifp_users_user_update_groups_list_done,
+                            sbus_req);
+
+    return EOK;
+}
+
+static void ifp_users_user_update_groups_list_done(struct tevent_req *req)
+{
+    DBusError *error;
+    struct sbus_request *sbus_req;
+    errno_t ret;
+
+    sbus_req = tevent_req_callback_data(req, struct sbus_request);
+
+    ret = cache_req_initgr_by_name_recv(sbus_req, req, NULL, NULL, NULL);
+    talloc_zfree(req);
+    if (ret == ENOENT) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND,
+                               "User not found");
+        goto done;
+    } else if (ret != EOK) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+                               "user [%d]: %s\n", ret, sss_strerror(ret));
+        goto done;
+    }
+
+done:
+    if (ret != EOK) {
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    iface_ifp_users_user_UpdateGroupsList_finish(sbus_req);
+    return;
+}
+
+void ifp_users_user_get_name(struct sbus_request *sbus_req,
+                             void *data,
+                             const char **_out)
+{
+    ifp_users_get_as_string(sbus_req, data, SYSDB_NAME, _out);
+}
+
+void ifp_users_user_get_uid_number(struct sbus_request *sbus_req,
+                                   void *data,
+                                   uint32_t *_out)
+{
+    ifp_users_get_as_uint32(sbus_req, data, SYSDB_UIDNUM, _out);
+}
+
+void ifp_users_user_get_gid_number(struct sbus_request *sbus_req,
+                                   void *data,
+                                   uint32_t *_out)
+{
+    ifp_users_get_as_uint32(sbus_req, data, SYSDB_GIDNUM, _out);
+}
+
+void ifp_users_user_get_gecos(struct sbus_request *sbus_req,
+                              void *data,
+                              const char **_out)
+{
+    ifp_users_get_as_string(sbus_req, data, SYSDB_GECOS, _out);
+}
+
+void ifp_users_user_get_home_directory(struct sbus_request *sbus_req,
+                                       void *data,
+                                       const char **_out)
+{
+    ifp_users_get_as_string(sbus_req, data, SYSDB_HOMEDIR, _out);
+}
+
+void ifp_users_user_get_login_shell(struct sbus_request *sbus_req,
+                                    void *data,
+                                    const char **_out)
+{
+    ifp_users_get_as_string(sbus_req, data, SYSDB_SHELL, _out);
+}
+
+void ifp_users_user_get_groups(struct sbus_request *sbus_req,
+                               void *data,
+                               const char ***_out,
+                               int *_size)
+{
+    struct ifp_ctx *ifp_ctx;
+    struct sss_domain_info *domain;
+    const char *username;
+    struct ldb_message *user;
+    struct ldb_result *res;
+    const char **out;
+    gid_t gid;
+    errno_t ret;
+    int i;
+
+    *_out = NULL;
+    *_size = 0;
+
+    ifp_ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ifp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return;
+    }
+
+    if (!ifp_is_user_attr_allowed(ifp_ctx, "groups")) {
+        DEBUG(SSSDBG_TRACE_ALL, "Attribute %s is not allowed\n",
+              SYSDB_MEMBEROF);
+        return;
+    }
+
+    ret = ifp_users_user_get(sbus_req, ifp_ctx, NULL, &domain, &user);
+    if (ret != EOK) {
+        return;
+    }
+
+    username = ldb_msg_find_attr_as_string(user, SYSDB_NAME, NULL);
+    if (username == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "User name is empty!\n");
+        return;
+    }
+
+    /* Run initgroups. */
+    ret = sysdb_initgroups_with_views(sbus_req, domain, username, &res);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get groups for %s@%s [%d]: %s\n",
+              username, domain->name, ret, sss_strerror(ret));
+        return;
+    }
+
+    out = talloc_zero_array(sbus_req, const char *, res->count);
+    if (out == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n");
+        return;
+    }
+
+    for (i = 0; i < res->count; i++) {
+        gid = sss_view_ldb_msg_find_attr_as_uint64(domain, res->msgs[i],
+                                                   SYSDB_GIDNUM, 0);
+        if (gid == 0) {
+            continue;
+        }
+
+        out[i] = ifp_groups_build_path_from_msg(out, domain, res->msgs[i]);
+        if (out[i] == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "ifp_groups_build_path() failed\n");
+            return;
+        }
+    }
+
+    *_out = out;
+    *_size = res->count;
+}
+
+void ifp_users_user_get_extra_attributes(struct sbus_request *sbus_req,
+                                         void *data,
+                                         hash_table_t **_out)
+{
+    struct ifp_ctx *ifp_ctx;
+    struct sss_domain_info *domain;
+    struct ldb_message **user;
+    struct ldb_message_element *el;
+    struct ldb_dn *basedn;
+    size_t count;
+    uid_t uid;
+    const char *filter;
+    const char **extra;
+    hash_table_t *table;
+    hash_key_t key;
+    hash_value_t value;
+    const char **values;
+    errno_t ret;
+    int hret;
+    int i;
+
+    *_out = NULL;
+
+    ifp_ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ifp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return;
+    }
+
+    extra = ifp_get_user_extra_attributes(sbus_req, ifp_ctx);
+    if (extra == NULL || extra[0] == NULL) {
+        DEBUG(SSSDBG_TRACE_ALL, "No extra attributes to return\n");
+        return;
+    }
+
+    ret = ifp_users_user_get(sbus_req, data, &uid, &domain, NULL);
+    if (ret != EOK) {
+        return;
+    }
+
+    basedn = sysdb_user_base_dn(sbus_req, domain);
+    if (basedn == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_user_base_dn() failed\n");
+        return;
+    }
+
+    filter = talloc_asprintf(sbus_req, "(&(%s=%s)(%s=%u))",
+                             SYSDB_OBJECTCLASS, SYSDB_USER_CLASS,
+                             SYSDB_UIDNUM, uid);
+    if (filter == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n");
+        return;
+    }
+
+    ret = sysdb_search_entry(sbus_req, domain->sysdb, basedn,
+                             LDB_SCOPE_ONELEVEL, filter,
+                             extra, &count, &user);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user [%d]: %s\n",
+              ret, sss_strerror(ret));
+        return;
+    }
+
+    if (count == 0) {
+        DEBUG(SSSDBG_TRACE_FUNC, "User %u not found!\n", uid);
+        return;
+    } else if (count > 1) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "More than one entry found!\n");
+        return;
+    }
+
+    ret = sss_hash_create(sbus_req, 10, &table);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table!\n");
+        return;
+    }
+
+    /* Read each extra attribute. */
+    for (i = 0; extra[i] != NULL; i++) {
+        el = ldb_msg_find_element(user[0], extra[i]);
+        if (el == NULL) {
+            DEBUG(SSSDBG_TRACE_ALL, "Attribute %s not found, skipping...\n",
+                  extra[i]);
+            continue;
+        }
+
+        values = sss_ldb_el_to_string_list(table, el);
+        if (values == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldb_el_to_string_list() failed\n");
+            return;
+        }
+
+        key.type = HASH_KEY_STRING;
+        key.str = talloc_strdup(table, extra[i]);
+        if (key.str == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n");
+            return;
+        }
+
+        value.type = HASH_VALUE_PTR;
+        value.ptr = values;
+
+        hret = hash_enter(table, &key, &value);
+        if (hret != HASH_SUCCESS) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Unable to insert entry "
+                 "into hash table: %d\n", hret);
+            return;
+        }
+    }
+
+    *_out = table;
+}
diff --git a/src/responder/ifp/ifp_users.h b/src/responder/ifp/ifp_users.h
index a0a079f482553e8b7b3fd874140cedfc29fdb7f8..7027ef14e51e3ca2bd92368fdfc10315edbf2fa8 100644
--- a/src/responder/ifp/ifp_users.h
+++ b/src/responder/ifp/ifp_users.h
@@ -54,4 +54,42 @@ int ifp_users_list_by_domain_and_name(struct sbus_request *sbus_req,
                                       const char *filter,
                                       uint32_t limit);
 
+/* org.freedesktop.sssd.infopipe.Users.User */
+
+int ifp_users_user_update_groups_list(struct sbus_request *req,
+                                      void *data);
+
+void ifp_users_user_get_name(struct sbus_request *sbus_req,
+                             void *data,
+                             const char **_out);
+
+void ifp_users_user_get_uid_number(struct sbus_request *sbus_req,
+                                   void *data,
+                                   uint32_t *_out);
+
+void ifp_users_user_get_gid_number(struct sbus_request *sbus_req,
+                                   void *data,
+                                   uint32_t *_out);
+
+void ifp_users_user_get_gecos(struct sbus_request *sbus_req,
+                              void *data,
+                              const char **_out);
+
+void ifp_users_user_get_home_directory(struct sbus_request *sbus_req,
+                                       void *data,
+                                       const char **_out);
+
+void ifp_users_user_get_login_shell(struct sbus_request *sbus_req,
+                                    void *data,
+                                    const char **_out);
+
+void ifp_users_user_get_groups(struct sbus_request *sbus_req,
+                               void *data,
+                               const char ***_out,
+                               int *_size);
+
+void ifp_users_user_get_extra_attributes(struct sbus_request *sbus_req,
+                                         void *data,
+                                         hash_table_t **_out);
+
 #endif /* IFP_USERS_H_ */
diff --git a/src/responder/ifp/ifpsrv.c b/src/responder/ifp/ifpsrv.c
index 3f5444186bf5878ca9fd15760f914892293a03b6..f804ea1978b6b32590c67fa34e00f94008b7b423 100644
--- a/src/responder/ifp/ifpsrv.c
+++ b/src/responder/ifp/ifpsrv.c
@@ -300,7 +300,7 @@ int ifp_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
-    ifp_ctx->user_whitelist = ifp_parse_attr_list(ifp_ctx, attr_list_str);
+    ifp_ctx->user_whitelist = ifp_parse_user_attr_list(ifp_ctx, attr_list_str);
     talloc_free(attr_list_str);
     if (ifp_ctx->user_whitelist == NULL) {
         DEBUG(SSSDBG_FATAL_FAILURE,
diff --git a/src/responder/ifp/ifpsrv_util.c b/src/responder/ifp/ifpsrv_util.c
index b6a0b168b35f3b3354a837cf9a2c1bc57d41324e..2246c5382981b39a2096e896f5bc22de29d45652 100644
--- a/src/responder/ifp/ifpsrv_util.c
+++ b/src/responder/ifp/ifpsrv_util.c
@@ -24,10 +24,11 @@
 #include "db/sysdb.h"
 #include "responder/ifp/ifp_private.h"
 
-#define IFP_DEFAULT_ATTRS {SYSDB_NAME, SYSDB_UIDNUM,   \
-                           SYSDB_GIDNUM, SYSDB_GECOS,  \
-                           SYSDB_HOMEDIR, SYSDB_SHELL, \
-                           NULL}
+#define IFP_USER_DEFAULT_ATTRS {SYSDB_NAME, SYSDB_UIDNUM,   \
+                                SYSDB_GIDNUM, SYSDB_GECOS,  \
+                                SYSDB_HOMEDIR, SYSDB_SHELL, \
+                                "groups", \
+                                NULL}
 
 errno_t ifp_req_create(struct sbus_request *dbus_req,
                        struct ifp_ctx *ifp_ctx,
@@ -175,13 +176,6 @@ errno_t ifp_add_ldb_el_to_dict(DBusMessageIter *iter_dict,
     return EOK;
 }
 
-const char **
-ifp_parse_attr_list(TALLOC_CTX *mem_ctx, const char *conf_str)
-{
-    const char *defaults[] = IFP_DEFAULT_ATTRS;
-
-    return parse_attr_list_ex(mem_ctx, conf_str, defaults);
-}
 
 bool
 ifp_attr_allowed(const char *whitelist[], const char *attr)
@@ -200,3 +194,79 @@ ifp_attr_allowed(const char *whitelist[], const char *attr)
 
     return (whitelist[i]) ? true : false;
 }
+
+const char **
+ifp_parse_user_attr_list(TALLOC_CTX *mem_ctx, const char *csv)
+{
+    static const char *defaults[] = IFP_USER_DEFAULT_ATTRS;
+
+    return parse_attr_list_ex(mem_ctx, csv, defaults);
+}
+
+const char **
+ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    const char *std[] = IFP_USER_DEFAULT_ATTRS;
+    const char **whitelist = ifp_ctx->user_whitelist;
+    const char **extra;
+    bool found;
+    int extra_num;
+    int i, j;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
+        return NULL;
+    }
+
+    for (i = 0; whitelist[i] != NULL; i++) {
+        /* Just count number of attributes in whitelist. */
+    }
+
+    extra = talloc_zero_array(tmp_ctx, const char *, i + 1);
+    if (extra == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n");
+        goto fail;
+    }
+
+    extra_num = 0;
+    for (i = 0; whitelist[i] != NULL; i++) {
+        found = false;
+        for (j = 0; std[j] != NULL; j++) {
+            if (strcmp(whitelist[i], std[j]) == 0) {
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) {
+            extra[extra_num] = talloc_strdup(extra, whitelist[i]);
+            if (extra[extra_num] == NULL) {
+                goto fail;
+            }
+
+            extra_num++;
+        }
+    }
+
+    extra = talloc_realloc(tmp_ctx, extra, const char *, extra_num + 1);
+    if (extra == NULL) {
+        goto fail;
+    }
+
+    talloc_steal(mem_ctx, extra);
+    talloc_free(tmp_ctx);
+    return extra;
+
+fail:
+    talloc_free(tmp_ctx);
+    return NULL;
+}
+
+bool
+ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr)
+{
+    return ifp_attr_allowed(ifp_ctx->user_whitelist, attr);
+}
+
diff --git a/src/responder/ifp/org.freedesktop.sssd.infopipe.conf b/src/responder/ifp/org.freedesktop.sssd.infopipe.conf
index 195e73aa9b00455af5732a079052cd49baae8624..79160bbcc2800cfb98dfa79dbf5e445f2e42a6e0 100644
--- a/src/responder/ifp/org.freedesktop.sssd.infopipe.conf
+++ b/src/responder/ifp/org.freedesktop.sssd.infopipe.conf
@@ -31,6 +31,7 @@
 
     <allow send_interface="org.freedesktop.sssd.infopipe"/>
     <allow send_interface="org.freedesktop.sssd.infopipe.Users"/>
+    <allow send_interface="org.freedesktop.sssd.infopipe.Users.User"/>
   </policy>
 
   <policy user="root">
diff --git a/src/tests/cmocka/test_ifp.c b/src/tests/cmocka/test_ifp.c
index a0fb5883cb9a136eb37b2880f04fe81fa74e135d..278111bb5d21331bac946d40a60da133bcf4e378 100644
--- a/src/tests/cmocka/test_ifp.c
+++ b/src/tests/cmocka/test_ifp.c
@@ -226,7 +226,7 @@ static void attr_parse_test(const char *expected[], const char *input)
     test_ctx = talloc_new(NULL);
     assert_non_null(test_ctx);
 
-    res = ifp_parse_attr_list(test_ctx, input);
+    res = ifp_parse_user_attr_list(test_ctx, input);
 
     if (expected) {
         /* Positive test */
-- 
1.9.3

