[libgdata] Bug 582026 – Access Control List functionality



commit 5d853ec5706129ce8af1bc0a2baae3dfed52d8d0
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun May 17 12:50:57 2009 +0100

    Bug 582026 â?? Access Control List functionality
    
    This adds a framework for controlling access control lists (ACLs). It
    includes GDataAccessHandler, an interface to be implemented by any GDataEntry
    which has an ACL. It also includes GDataAccessRule, a GDataEntry representing
    a single access control rule, initially written by Thibault Saunier
    <saunierthibault gmail com>.
    
    Full documentation is included, and test cases will be added per-service.
---
 docs/reference/gdata-docs.xml     |    6 +
 docs/reference/gdata-sections.txt |   41 ++++
 gdata/Makefile.am                 |    6 +-
 gdata/gdata-access-handler.c      |  415 +++++++++++++++++++++++++++++++++++++
 gdata/gdata-access-handler.h      |   76 +++++++
 gdata/gdata-access-rule.c         |  397 +++++++++++++++++++++++++++++++++++
 gdata/gdata-access-rule.h         |   77 +++++++
 gdata/gdata-service.h             |    4 +-
 gdata/gdata.h                     |    2 +
 gdata/gdata.symbols               |   12 +
 po/POTFILES.in                    |    1 +
 11 files changed, 1035 insertions(+), 2 deletions(-)

diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 4bb621e..f14206f 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -26,6 +26,12 @@
 	</chapter>
 
 	<chapter>
+		<title>Access Control List API</title>
+		<xi:include href="xml/gdata-access-handler.xml"/>
+		<xi:include href="xml/gdata-access-rule.xml"/>
+	</chapter>
+
+	<chapter>
 		<title>Namespace API</title>
 		<xi:include href="xml/gdata-atom.xml"/>
 		<xi:include href="xml/gdata-gdata.xml"/>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 79a1915..c49ecc4 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -628,3 +628,44 @@ gdata_youtube_uploader_get_type
 <SUBSECTION Private>
 GDataYouTubeQueryPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-access-handler</FILE>
+<TITLE>GDataAccessHandler</TITLE>
+GDataAccessHandler
+GDataAccessHandlerIface
+gdata_access_handler_get_rules
+gdata_access_handler_insert_rule
+gdata_access_handler_update_rule
+gdata_access_handler_delete_rule
+<SUBSECTION Standard>
+gdata_access_handler_get_type
+GDATA_ACCESS_HANDLER
+GDATA_ACCESS_HANDLER_CLASS
+GDATA_ACCESS_HANDLER_GET_IFACE
+GDATA_IS_ACCESS_HANDLER
+GDATA_TYPE_ACCESS_HANDLER
+</SECTION>
+
+<SECTION>
+<FILE>gdata-access-rule</FILE>
+<TITLE>GDataAccessRule</TITLE>
+GDataAccessRule
+GDataAccessRuleClass
+gdata_access_rule_new
+gdata_access_rule_new_from_xml
+gdata_access_rule_get_role
+gdata_access_rule_set_role
+gdata_access_rule_get_scope
+gdata_access_rule_set_scope
+<SUBSECTION Standard>
+gdata_access_rule_get_type
+GDATA_ACCESS_RULE
+GDATA_ACCESS_RULE_CLASS
+GDATA_ACCESS_RULE_GET_CLASS
+GDATA_IS_ACCESS_RULE
+GDATA_IS_ACCESS_RULE_CLASS
+GDATA_TYPE_ACCESS_RULE
+<SUBSECTION Private>
+GDataAccessRulePrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index 8551fdc..03f0660 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -50,7 +50,9 @@ gdata_headers = \
 	gdata-atom.h		\
 	gdata-media-rss.h	\
 	gdata-gdata.h		\
-	gdata-parser.h
+	gdata-parser.h		\
+	gdata-access-handler.h	\
+	gdata-access-rule.h
 
 gdataincludedir = $(pkgincludedir)/gdata
 gdatainclude_HEADERS = \
@@ -70,6 +72,8 @@ libgdata_la_SOURCES = \
 	gdata-media-rss.c	\
 	gdata-gdata.c		\
 	gdata-parser.c		\
+	gdata-access-handler.c	\
+	gdata-access-rule.c	\
 	gdata-private.h
 
 libgdata_la_CPPFLAGS = \
diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c
new file mode 100644
index 0000000..6587220
--- /dev/null
+++ b/gdata/gdata-access-handler.c
@@ -0,0 +1,415 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ * 
+ * GData Client 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.
+ *
+ * GData Client 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 GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-access-handler
+ * @short_description: GData access handler interface
+ * @stability: Unstable
+ * @include: gdata/gdata-access-handler.h
+ *
+ * #GDataAccessHandler is an interface which can be implemented by #GDataEntry<!-- -->s which can have their permissions controlled by an
+ * access control list (ACL). It has a set of methods which allow the #GDataAccessRule<!-- -->s for the access handler/entry to be retrieved,
+ * added, modified and deleted, with immediate effect.
+ *
+ * When implementing the interface, classes must implement an <function>is_owner_rule</function> function.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+#include "gdata-access-handler.h"
+#include "gdata-private.h"
+#include "gdata-access-rule.h"
+
+GType
+gdata_access_handler_get_type (void)
+{
+	static GType access_handler_type = 0;
+
+	if (!access_handler_type) {
+		access_handler_type = g_type_register_static_simple (G_TYPE_INTERFACE, "GDataAccessHandler",
+								     sizeof (GDataAccessHandlerIface),
+								     NULL, 0, NULL, 0);
+	}
+
+	return access_handler_type;
+}
+
+/**
+ * gdata_access_handler_get_rules:
+ * @self: a #GDataAccessHandler
+ * @service: a #GDataService
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @progress_callback: a #GDataQueryProgressCallback to call when a rule is loaded, or %NULL
+ * @progress_user_data: data to pass to the @progress_callback function
+ * @error: a #GError, or %NULL
+ *
+ * Retrieves a #GDataFeed containing all the access rules which apply to the given #GDataAccessHandler. Only the owner of a #GDataAccessHandler may
+ * view its rule feed.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * A %GDATA_SERVICE_ERROR_WITH_QUERY will be returned if the server indicates there is a problem with the query.
+ *
+ * For each rule in the response feed, @progress_callback will be called in the main thread. If there was an error parsing the XML response,
+ * a #GDataParserError will be returned.
+ *
+ * Return value: a #GDataFeed of access control rules, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.3.0
+ **/
+GDataFeed *
+gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable, GDataQueryProgressCallback progress_callback,
+				gpointer progress_user_data, GError **error)
+{
+	GDataServiceClass *klass;
+	GDataFeed *feed;
+	GDataLink *link;
+	SoupMessage *message;
+	guint status;
+
+	/* TODO: async version */
+	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
+	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+
+	/* Get the ACL URI */
+	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
+	g_assert (link != NULL);
+	message = soup_message_new (SOUP_METHOD_GET, link->href);
+
+	/* Make sure subclasses set their headers */
+	klass = GDATA_SERVICE_GET_CLASS (service);
+	if (klass->append_query_headers != NULL)
+		klass->append_query_headers (service, message);
+
+	/* Send the message */
+	status = _gdata_service_send_message (service, message, error);
+	if (status == SOUP_STATUS_NONE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	/* Check for cancellation */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	if (status != 200) {
+		/* Error */
+		g_assert (klass->parse_error_response != NULL);
+		klass->parse_error_response (service, GDATA_SERVICE_ERROR_WITH_QUERY, status, message->reason_phrase, message->response_body->data,
+					     message->response_body->length, error);
+		g_object_unref (message);
+		return NULL;
+	}
+
+	g_assert (message->response_body->data != NULL);
+
+	feed = _gdata_feed_new_from_xml (message->response_body->data, message->response_body->length, GDATA_TYPE_ACCESS_RULE,
+					 progress_callback, progress_user_data, error);
+	g_object_unref (message);
+
+	return feed;
+}
+
+/**
+ * gdata_access_handler_insert_rule:
+ * @self: a #GDataAccessHandler
+ * @service: a #GDataService
+ * @rule: the #GDataAccessRule to insert
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Inserts @rule in the access control list of the #GDataAccessHandler.
+ *
+ * The service will return an updated version of the rule, which is the return value of this function on success.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If the rule is marked as already having been inserted a %GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED error will be returned immediately
+ * (there will be no network requests).
+ *
+ * If there is an error inserting the rule, a %GDATA_SERVICE_ERROR_WITH_INSERTION error will be returned.
+ *
+ * Return value: an updated #GDataAccessRule, or %NULL
+ *
+ * Since: 0.3.0
+ **/
+GDataAccessRule *
+gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
+{
+	GDataServiceClass *klass;
+	GDataAccessRule *updated_rule;
+	GDataLink *link;
+	SoupMessage *message;
+	gchar *upload_data;
+	guint status;
+
+	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
+	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), NULL);
+
+	if (gdata_entry_is_inserted (GDATA_ENTRY (rule)) == TRUE) {
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
+				     _("The rule has already been inserted."));
+		return NULL;
+	}
+
+	/* Get the ACL URI */
+	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
+	g_assert (link != NULL);
+	message = soup_message_new (SOUP_METHOD_POST, link->href);
+
+	/* Make sure subclasses set their headers */
+	klass = GDATA_SERVICE_GET_CLASS (service);
+	if (klass->append_query_headers != NULL)
+		klass->append_query_headers (service, message);
+
+	/* Append the data */
+	upload_data = gdata_entry_get_xml (GDATA_ENTRY (rule));
+	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
+
+	/* Send the message */
+	status = _gdata_service_send_message (service, message, error);
+	if (status == SOUP_STATUS_NONE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	/* Check for cancellation */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	if (status != 201) {
+		/* Error */
+		g_assert (klass->parse_error_response != NULL);
+		klass->parse_error_response (service, GDATA_SERVICE_ERROR_WITH_INSERTION, status, message->reason_phrase, message->response_body->data,
+					     message->response_body->length, error);
+		g_object_unref (message);
+		return NULL;
+	}
+
+	/* Build the updated entry */
+	g_assert (message->response_body->data != NULL);
+
+	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	updated_rule = GDATA_ACCESS_RULE (_gdata_entry_new_from_xml (G_OBJECT_TYPE (rule), message->response_body->data,
+					  message->response_body->length, error));
+	g_object_unref (message);
+
+	return updated_rule;
+}
+
+static SoupMessage *
+get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, const gchar *method)
+{
+	GDataLink *link;
+	SoupMessage *message;
+	GString *uri_string;
+	gchar *uri;
+	const gchar *scope_type, *scope_value;
+
+	/* Get the edit URI */
+	link = gdata_entry_look_up_link (GDATA_ENTRY (rule), "edit");
+	if (link != NULL)
+		return soup_message_new (method, link->href);
+
+	/* Try building the URI instead */
+	link = gdata_entry_look_up_link (GDATA_ENTRY (access_handler), "http://schemas.google.com/acl/2007#accessControlList";);
+	g_assert (link != NULL);
+	gdata_access_rule_get_scope (rule, &scope_type, &scope_value);
+
+	uri_string = g_string_sized_new (strlen (link->href) + 30);
+	g_string_append_printf (uri_string, "%s/", link->href);
+	g_string_append_uri_escaped (uri_string, scope_type, NULL, TRUE);
+	if (scope_value != NULL) {
+		g_string_append (uri_string, "%3A");
+		g_string_append_uri_escaped (uri_string, scope_value, NULL, TRUE);
+	}
+
+	uri = g_string_free (uri_string, FALSE);
+	message = soup_message_new (method, uri);
+	g_free (uri);
+
+	return message;
+}
+
+/**
+ * gdata_access_handler_update_rule:
+ * @self: a #GDataAccessHandler
+ * @service: a #GDataService
+ * @rule: the #GDataAccessRule to update
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Updates @rule in the access control list of the #GDataAccessHandler.
+ *
+ * The service will return an updated version of the rule, which is the return value of this function on success.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error updating the rule, a %GDATA_SERVICE_ERROR_WITH_UPDATE error will be returned.
+ *
+ * Return value: an updated #GDataAccessRule, or %NULL
+ *
+ * Since: 0.3.0
+ **/
+GDataAccessRule *
+gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
+{
+	GDataServiceClass *klass;
+	GDataAccessRule *updated_rule;
+	SoupMessage *message;
+	gchar *upload_data;
+	guint status;
+
+	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
+	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), NULL);
+
+	message = get_soup_message (self, rule, SOUP_METHOD_PUT);
+
+	/* Make sure subclasses set their headers */
+	klass = GDATA_SERVICE_GET_CLASS (service);
+	if (klass->append_query_headers != NULL)
+		klass->append_query_headers (service, message);
+
+	/* Looks like ACLs don't support ETags */
+
+	/* Append the data */
+	upload_data = gdata_entry_get_xml (GDATA_ENTRY (rule));
+	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
+
+	/* Send the message */
+	status = _gdata_service_send_message (service, message, error);
+	if (status == SOUP_STATUS_NONE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	/* Check for cancellation */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+		g_object_unref (message);
+		return NULL;
+	}
+
+	if (status != 200) {
+		/* Error */
+		g_assert (klass->parse_error_response != NULL);
+		klass->parse_error_response (service, GDATA_SERVICE_ERROR_WITH_UPDATE, status, message->reason_phrase, message->response_body->data,
+					     message->response_body->length, error);
+		g_object_unref (message);
+		return NULL;
+	}
+
+	/* Build the updated entry */
+	g_assert (message->response_body->data != NULL);
+
+	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	updated_rule = GDATA_ACCESS_RULE (_gdata_entry_new_from_xml (G_OBJECT_TYPE (rule), message->response_body->data,
+								     message->response_body->length, error));
+	g_object_unref (message);
+
+	return updated_rule;
+}
+
+/**
+ * gdata_access_handler_delete_rule:
+ * @self: a #GDataAccessHandler
+ * @service: a #GDataService
+ * @rule: the #GDataAccessRule to delete
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Deletes @rule from the access control list of the #GDataAccessHandler.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error deleting the rule, a %GDATA_SERVICE_ERROR_WITH_DELETION error will be returned, unless the @rule was the owner's
+ * rule; in which case, %GDATA_SERVICE_ERROR_FORBIDDEN will be returned without any network activity.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ *
+ * Since: 0.3.0
+ **/
+gboolean
+gdata_access_handler_delete_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
+{
+	GDataServiceClass *klass;
+	GDataAccessHandlerIface *iface;
+	SoupMessage *message;
+	guint status;
+
+	g_return_val_if_fail (GDATA_IS_ENTRY (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_SERVICE (service), FALSE);
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), FALSE);
+
+	/* The owner of the access handler can't be deleted */
+	iface = GDATA_ACCESS_HANDLER_GET_IFACE (self);
+	g_assert (iface->is_owner_rule != NULL);
+	if (iface->is_owner_rule (rule) == TRUE) {
+		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN,
+				     _("The owner's rule may not be deleted."));
+		return FALSE;
+	}
+
+	message = get_soup_message (self, rule, SOUP_METHOD_DELETE);
+
+	/* Make sure subclasses set their headers */
+	klass = GDATA_SERVICE_GET_CLASS (service);
+	if (klass->append_query_headers != NULL)
+		klass->append_query_headers (service, message);
+
+	/* Looks like ACLs don't support ETags */
+
+	/* Send the message */
+	status = _gdata_service_send_message (service, message, error);
+	if (status == SOUP_STATUS_NONE) {
+		g_object_unref (message);
+		return FALSE;
+	}
+
+	/* Check for cancellation */
+	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+		g_object_unref (message);
+		return FALSE;
+	}
+
+	if (status != 200) {
+		/* Error */
+		g_assert (klass->parse_error_response != NULL);
+		klass->parse_error_response (service, GDATA_SERVICE_ERROR_WITH_DELETION, status, message->reason_phrase, message->response_body->data,
+					     message->response_body->length, error);
+		g_object_unref (message);
+		return FALSE;
+	}
+
+	g_object_unref (message);
+
+	return TRUE;
+}
diff --git a/gdata/gdata-access-handler.h b/gdata/gdata-access-handler.h
new file mode 100644
index 0000000..ceb8790
--- /dev/null
+++ b/gdata/gdata-access-handler.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ * 
+ * GData Client 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.
+ *
+ * GData Client 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 GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_ACCESS_HANDLER_H
+#define GDATA_ACCESS_HANDLER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-feed.h>
+#include <gdata/gdata-service.h>
+#include <gdata/gdata-access-rule.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_ACCESS_HANDLER		(gdata_access_handler_get_type ())
+#define GDATA_ACCESS_HANDLER(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_ACCESS_HANDLER, GDataAccessHandler))
+#define GDATA_ACCESS_HANDLER_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_ACCESS_HANDLER, GDataAccessHandlerIface))
+#define GDATA_IS_ACCESS_HANDLER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_ACCESS_HANDLER))
+#define GDATA_ACCESS_HANDLER_GET_IFACE(o)	(G_TYPE_INSTANCE_GET_INTERFACE ((o), GDATA_TYPE_ACCESS_HANDLER, GDataAccessHandlerIface))
+
+/**
+ * GDataAccessHandler:
+ *
+ * All the fields in the #GDataAccessHandler structure are private and should never be accessed directly.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct _GDataAccessHandler		GDataAccessHandler; /* dummy typedef */
+
+/**
+ * GDataAccessHandlerIface:
+ * @parent: the parent type
+ * @is_owner_rule: a function to return whether the given #GDataAccessRule has the role of an owner (of a #GDataAccessHandler).
+ *
+ * The class structure for the #GDataAccessHandler interface.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct {
+	GTypeInterface parent;
+
+	gboolean (*is_owner_rule) (GDataAccessRule *rule);
+} GDataAccessHandlerIface;
+
+GType gdata_access_handler_get_type (void) G_GNUC_CONST;
+
+GDataFeed *gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable,
+					   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+					   GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataAccessRule *gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule,
+						   GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataAccessRule *gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule,
+						   GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gboolean gdata_access_handler_delete_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule,
+					   GCancellable *cancellable, GError **error);
+
+G_END_DECLS
+
+#endif /* !GDATA_ACCESS_HANDLER_H */
diff --git a/gdata/gdata-access-rule.c b/gdata/gdata-access-rule.c
new file mode 100644
index 0000000..cd2a50f
--- /dev/null
+++ b/gdata/gdata-access-rule.c
@@ -0,0 +1,397 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-access-rule
+ * @short_description: GData access rule object
+ * @stability: Unstable
+ * @include: gdata/gdata-access-rule.h
+ *
+ * #GDataAccessRule is a subclass of #GDataEntry to represent a generic access rule from an access control list (ACL).
+ * It is returned by the ACL methods implemented in the #GDataAccessHandler interface.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <libxml/parser.h>
+#include <string.h>
+
+#include "gdata-access-rule.h"
+#include "gdata-gdata.h"
+#include "gdata-parser.h"
+#include "gdata-types.h"
+#include "gdata-private.h"
+
+static void gdata_access_rule_finalize (GObject *object);
+static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
+static void get_xml (GDataEntry *entry, GString *xml_string);
+static void gdata_access_rule_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void gdata_access_rule_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+
+struct _GDataAccessRulePrivate {
+	gchar *role;
+	gchar *scope_type; 
+	gchar *scope_value;
+};
+
+enum {
+	PROP_ROLE = 1,
+	PROP_SCOPE_TYPE,
+	PROP_SCOPE_VALUE,
+};
+
+G_DEFINE_TYPE (GDataAccessRule, gdata_access_rule, GDATA_TYPE_ENTRY)
+#define GDATA_ACCESS_RULE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_ACCESS_RULE, GDataAccessRulePrivate))
+
+static void
+gdata_access_rule_class_init (GDataAccessRuleClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataAccessRulePrivate));
+
+	gobject_class->finalize = gdata_access_rule_finalize;
+	gobject_class->set_property = gdata_access_rule_set_property; 
+	gobject_class->get_property = gdata_access_rule_get_property; 
+
+	entry_class->get_xml = get_xml;
+	entry_class->parse_xml = parse_xml;
+	entry_class->get_namespaces = get_namespaces;
+
+	/**
+	 * GDataAccessRule:role:
+	 *
+	 * The role of the person concerned by this ACL.
+	 *
+	 * Since: 0.3.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_ROLE,
+				g_param_spec_string ("role",
+					"Role", "The role of the person concerned by this ACL.",
+					NULL,
+					G_PARAM_READWRITE ));
+
+	/**
+	 * GDataAccessRule:scope-type:
+	 *
+	 * Specifies to whom this access rule applies.
+	 *
+	 * Since: 0.3.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_SCOPE_TYPE,
+				g_param_spec_string ("scope-type",
+					"Scope type", "Specifies to whom this access rule applies.",
+					NULL,
+					G_PARAM_READWRITE ));
+
+	/**
+	 * GDataAccessRule:scope-value:
+	 *
+	 * A value representing the user who is represented by the access rule, such as an
+	 * e-mail address for users, or a domain name for domains.
+	 *
+	 * Since: 0.3.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_SCOPE_VALUE,
+				g_param_spec_string ("scope-value",
+					"Scope value", "The scope value for this access rule.",
+					NULL,
+					G_PARAM_READWRITE ));
+}
+
+/**
+ * gdata_access_rule_new:
+ * @id: the access rule's ID, or %NULL
+ *
+ * Creates a new #GDataAccessRule with the given ID and default properties.
+ *
+ * Return value: a new #GDataAccessRule; unref with g_object_unref()
+ *
+ * Since: 0.3.0
+ **/
+GDataAccessRule *
+gdata_access_rule_new (const gchar *id)
+{
+	return g_object_new (GDATA_TYPE_ACCESS_RULE, "id", id, NULL);
+}
+
+/**
+ * gdata_access_rule_new_from_xml:
+ * @xml: an XML string
+ * @length: the length in characters of @xml, or %-1
+ * @error: a #GError, or %NULL
+ *
+ * Creates a new #GDataAccessRule from an XML string. If @length is %-1, the length of
+ * the string will be calculated.
+ *
+ * Errors from #GDataParserError can be returned if problems are found in the XML.
+ *
+ * Return value: a new #GDataAccessRule, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.3.0
+ **/
+GDataAccessRule *
+gdata_access_rule_new_from_xml (const gchar *xml, gint length, GError **error)
+{
+	return GDATA_ACCESS_RULE (_gdata_entry_new_from_xml (GDATA_TYPE_ACCESS_RULE, xml, length, error));
+}
+
+static void
+gdata_access_rule_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataAccessRule *self = GDATA_ACCESS_RULE (object);
+
+	switch (property_id) {
+		case PROP_ROLE:
+			gdata_access_rule_set_role (self, g_value_get_string (value));
+			break;
+		case PROP_SCOPE_TYPE:
+			g_free (self->priv->scope_type);
+			self->priv->scope_type = g_value_dup_string (value);
+			g_object_notify (object, "scope-type");
+			break;
+		case PROP_SCOPE_VALUE:
+			g_free (self->priv->scope_value);
+			self->priv->scope_value = g_value_dup_string (value);
+			g_object_notify (object, "scope-value");
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_access_rule_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataAccessRulePrivate *priv = GDATA_ACCESS_RULE_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_ROLE:
+			g_value_set_string (value, priv->role);
+			break;
+		case PROP_SCOPE_TYPE:
+			g_value_set_string (value, priv->scope_type);
+			break;
+		case PROP_SCOPE_VALUE:
+			g_value_set_string (value, priv->scope_value);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_access_rule_init (GDataAccessRule *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_ACCESS_RULE, GDataAccessRulePrivate);
+}
+
+static void
+gdata_access_rule_finalize (GObject *object)
+{
+	GDataAccessRulePrivate *priv = GDATA_ACCESS_RULE_GET_PRIVATE (object);
+
+	g_free (priv->role);
+	g_free (priv->scope_type); 
+	g_free (priv->scope_value);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_access_rule_parent_class)->finalize (object);
+}
+
+static gboolean
+parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+{
+	GDataAccessRule *self = GDATA_ACCESS_RULE (entry);
+
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (self), FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	if (xmlStrcmp (node->name, (xmlChar*) "role") == 0) {
+		/* gAcl:role */
+		xmlChar *role = xmlGetProp (node, (xmlChar*) "value");
+		if (role == NULL)
+			return gdata_parser_error_required_property_missing ("gAcl:role", "value", error);
+		gdata_access_rule_set_role (self, (gchar*) role);
+		xmlFree (role);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "scope") == 0) {
+		/* gAcl:scope */
+		xmlChar *scope_type, *scope_value;
+
+		scope_type = xmlGetProp (node, (xmlChar*) "type");
+		if (scope_type == NULL)
+			return gdata_parser_error_required_property_missing ("gAcl:scope", "type", error);
+
+		scope_value = xmlGetProp (node, (xmlChar*) "value");
+		gdata_access_rule_set_scope (self, (gchar*) scope_type, (gchar*) scope_value);
+		xmlFree (scope_type);
+		xmlFree (scope_value);
+	} else if (GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+		/* Error! */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+get_xml (GDataEntry *entry, GString *xml_string)
+{
+	GDataCategory *category;
+	GDataAccessRulePrivate *priv = GDATA_ACCESS_RULE (entry)->priv;
+
+	/* Ensure we have the correct category/kind */
+	category = gdata_category_new ("http://schemas.google.com/acl/2007#accessRule";, "http://schemas.google.com/g/2005#kind";, NULL);
+	gdata_entry_add_category (entry, category);
+
+	/* So it's valid Atom, set the title if one doesn't already exist */
+	if (gdata_entry_get_title (entry) == NULL)
+		gdata_entry_set_title (entry, priv->role);
+
+	/* Chain up to the parent class */
+	GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->get_xml (entry, xml_string);
+
+	if (priv->role != NULL)
+		/* gAcl:role */
+		g_string_append_printf (xml_string, "<gAcl:role value='%s'/>", priv->role);
+
+	if (priv->scope_value != NULL){
+		/* gAcl:scope */
+		if (priv->scope_type != NULL)
+			g_string_append_printf (xml_string, "<gAcl:scope type='%s' value='%s'/>", priv->scope_type, priv->scope_value);
+		else
+			g_string_append_printf (xml_string, "<gAcl:scope value='%s'/>", priv->scope_value);
+	}
+}
+
+static void
+get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+{
+	/* Chain up to the parent class */
+	GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->get_namespaces (entry, namespaces);
+
+	g_hash_table_insert (namespaces, (gchar*) "gAcl", (gchar*) "http://schemas.google.com/acl/2007";); 
+}
+
+/**
+ * gdata_access_rule_set_role:
+ * @self: a #GDataAccessRule
+ * @role: a new role, or %NULL
+ *
+ * Sets the #GDataAccessRule:role property to @role.
+ *
+ * Set @role to %NULL to unset the property in the access rule.
+ *
+ * Since: 0.3.0
+ **/
+void 
+gdata_access_rule_set_role (GDataAccessRule *self, const gchar *role)
+{
+	g_return_if_fail (GDATA_IS_ACCESS_RULE (self));
+	g_free (self->priv->role);
+	self->priv->role = g_strdup (role);
+	g_object_notify (G_OBJECT (self), "role");
+}
+
+/**
+ * gdata_access_rule_get_role:
+ * @self: a #GDataAccessRule
+ *
+ * Gets the #GDataAccessRule:role property.
+ *
+ * Return value: the access rule's role, or %NULL
+ *
+ * Since: 0.3.0
+ **/
+const gchar *
+gdata_access_rule_get_role (GDataAccessRule *self)
+{
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (self), NULL);
+	return self->priv->role;
+}
+
+
+/**
+ * gdata_access_rule_set_scope:
+ * @self: a #GDataAccessRule
+ * @type: a new scope type
+ * @value: a new scope value, or %NULL
+ *
+ * Sets the #GDataAccessRule:scope-type property to @type and the #GDataAccessRule:scope-value property to @value.
+ *
+ * Set @scope_value to %NULL to unset the #GDataAccessRule:scope-value property in the access rule. @type cannot
+ * be %NULL. @scope_value must be %NULL if @type is <literal>default</literal>, and non-%NULL otherwise.
+ *
+ * See the <ulink type="http" url="http://code.google.com/apis/calendar/docs/2.0/reference.html#gacl_reference";>online documentation</ulink>
+ * for more information.
+ *
+ * Since: 0.3.0
+ **/
+void
+gdata_access_rule_set_scope (GDataAccessRule *self, const gchar *type, const gchar *value)
+{
+	g_return_if_fail (GDATA_IS_ACCESS_RULE (self));
+	g_return_if_fail (type != NULL);
+
+	/* Validate stuff first */
+	if (strcmp (type, "default") == 0)
+		g_return_if_fail (value == NULL);
+	else
+		g_return_if_fail (value != NULL);
+
+	g_free (self->priv->scope_type);
+	self->priv->scope_type = g_strdup (type);
+
+	g_free (self->priv->scope_value);
+	self->priv->scope_value = g_strdup (value);
+
+	g_object_freeze_notify (G_OBJECT (self));
+	g_object_notify (G_OBJECT (self), "scope-type");
+	g_object_notify (G_OBJECT (self), "scope-value");
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gdata_access_rule_get_scope:
+ * @self: a #GDataAccessRule
+ * @type: return location for the scope type, or %NULL
+ * @value: return location for the scope value, or %NULL
+ *
+ * Gets the #GDataAccessRule:scope-type and #GDataAccessRule:scope-value properties.
+ *
+ * Since: 0.3.0
+ **/
+void
+gdata_access_rule_get_scope (GDataAccessRule *self, const gchar **type, const gchar **value)
+{
+	g_return_if_fail (GDATA_IS_ACCESS_RULE (self));
+	if (type != NULL)
+		*type = self->priv->scope_type;
+	if (value != NULL)
+		*value = self->priv->scope_value;
+}
diff --git a/gdata/gdata-access-rule.h b/gdata/gdata-access-rule.h
new file mode 100644
index 0000000..c11d240
--- /dev/null
+++ b/gdata/gdata-access-rule.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Thibault Saunier 2009 <saunierthibault gmail com>
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_ACCESS_RULE_H
+#define GDATA_ACCESS_RULE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-entry.h>
+#include <gdata/gdata-gdata.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_ACCESS_RULE		(gdata_access_rule_get_type ())
+#define GDATA_ACCESS_RULE(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_ACCESS_RULE, GDataAccessRule))
+#define GDATA_ACCESS_RULE_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_ACCESS_RULE, GDataAccessRuleClass))
+#define GDATA_IS_ACCESS_RULE(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_ACCESS_RULE))
+#define GDATA_IS_ACCESS_RULE_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_ACCESS_RULE))
+#define GDATA_ACCESS_RULE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_ACCESS_RULE, GDataAccessRuleClass))
+
+typedef struct _GDataAccessRulePrivate	GDataAccessRulePrivate;
+
+/**
+ * GDataAccessRule:
+ *
+ * All the fields in the #GDataAccessRule structure are private and should never be accessed directly.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct {
+	GDataEntry parent;
+	GDataAccessRulePrivate *priv;
+} GDataAccessRule;
+
+/**
+ * GDataAccessRuleClass:
+ *
+ * All the fields in the #GDataAccessRuleClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataEntryClass parent;
+} GDataAccessRuleClass;
+
+GType gdata_access_rule_get_type (void) G_GNUC_CONST;
+
+GDataAccessRule *gdata_access_rule_new (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
+GDataAccessRule *gdata_access_rule_new_from_xml (const gchar *xml, gint length, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
+const gchar *gdata_access_rule_get_role (GDataAccessRule *self);
+void gdata_access_rule_set_role (GDataAccessRule *self, const gchar *role);
+void gdata_access_rule_get_scope (GDataAccessRule *self, const gchar **type, const gchar **value);
+void gdata_access_rule_set_scope (GDataAccessRule *self, const gchar *type, const gchar *value);
+
+G_END_DECLS
+
+#endif /* !GDATA_ACCESS_RULE_H */
diff --git a/gdata/gdata-service.h b/gdata/gdata-service.h
index 81c51cb..7f75df5 100644
--- a/gdata/gdata-service.h
+++ b/gdata/gdata-service.h
@@ -42,6 +42,7 @@ G_BEGIN_DECLS
  * @GDATA_SERVICE_ERROR_NOT_FOUND: A requested resource (feed or entry) was not found on the server
  * @GDATA_SERVICE_ERROR_CONFLICT: There was a conflict when updating an entry on the server; the server-side copy was modified inbetween downloading
  * and uploading the modified entry
+ * @GDATA_SERVICE_ERROR_FORBIDDEN: Generic error for a forbidden action (not due to having insufficient permissions)
  *
  * Error codes for #GDataService operations.
  **/
@@ -55,7 +56,8 @@ typedef enum {
 	GDATA_SERVICE_ERROR_WITH_UPDATE,
 	GDATA_SERVICE_ERROR_WITH_DELETION,
 	GDATA_SERVICE_ERROR_NOT_FOUND,
-	GDATA_SERVICE_ERROR_CONFLICT
+	GDATA_SERVICE_ERROR_CONFLICT,
+	GDATA_SERVICE_ERROR_FORBIDDEN
 } GDataServiceError;
 
 /**
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 20fde3c..b51d3c5 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -28,6 +28,8 @@
 #include <gdata/gdata-parser.h>
 #include <gdata/gdata-query.h>
 #include <gdata/gdata-enums.h>
+#include <gdata/gdata-access-handler.h>
+#include <gdata/gdata-access-rule.h>
 
 /* Namespaces */
 #include <gdata/gdata-atom.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index d5353f4..30ccb08 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -335,3 +335,15 @@ gdata_contacts_contact_remove_group
 gdata_contacts_contact_is_group_deleted
 gdata_contacts_contact_get_groups
 gdata_contacts_contact_is_deleted
+gdata_access_handler_get_type
+gdata_access_handler_get_rules
+gdata_access_handler_insert_rule
+gdata_access_handler_update_rule
+gdata_access_handler_delete_rule
+gdata_access_rule_get_type
+gdata_access_rule_new
+gdata_access_rule_new_from_xml
+gdata_access_rule_get_role
+gdata_access_rule_set_role
+gdata_access_rule_get_scope
+gdata_access_rule_set_scope
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1f25452..180770d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,6 +1,7 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 [encoding: UTF-8]
+gdata/gdata-access-handler.c
 gdata/gdata-entry.c
 gdata/gdata-feed.c
 gdata/gdata-parser.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]