From 2cffcfbc03dfa4463bdfd72de0341000509bcec3 Mon Sep 17 00:00:00 2001
From: Thomas Tanghus <thomas@tanghus.net>
Date: Thu, 23 Aug 2012 22:02:38 +0200
Subject: [PATCH] Permission checking for shared addressbooks/contacts.

---
 apps/contacts/ajax/contact/addproperty.php    |   6 +-
 apps/contacts/ajax/contact/delete.php         |  12 +-
 apps/contacts/ajax/contact/deleteproperty.php |   6 +-
 apps/contacts/ajax/contact/list.php           |  16 ++-
 apps/contacts/ajax/contact/saveproperty.php   |   7 +-
 apps/contacts/js/contacts.js                  | 108 +++++++++++++++---
 apps/contacts/lib/share/addressbook.php       |   1 +
 apps/contacts/lib/vcard.php                   |  48 +++++---
 8 files changed, 165 insertions(+), 39 deletions(-)

diff --git a/apps/contacts/ajax/contact/addproperty.php b/apps/contacts/ajax/contact/addproperty.php
index 89f7a2bd9a..2b80ebd58b 100644
--- a/apps/contacts/ajax/contact/addproperty.php
+++ b/apps/contacts/ajax/contact/addproperty.php
@@ -154,8 +154,10 @@ foreach ($parameters as $key=>$element) {
 }
 $checksum = md5($vcard->children[$line]->serialize());
 
-if(!OC_Contacts_VCard::edit($id, $vcard)) {
-	bailOut($l10n->t('Error adding contact property: '.$name));
+try {
+	OC_Contacts_VCard::edit($id, $vcard);
+} catch(Exception $e) {
+	bailOut($e->getMessage());
 }
 
 OCP\JSON::success(array(
diff --git a/apps/contacts/ajax/contact/delete.php b/apps/contacts/ajax/contact/delete.php
index def98b3670..e73f34f898 100644
--- a/apps/contacts/ajax/contact/delete.php
+++ b/apps/contacts/ajax/contact/delete.php
@@ -4,6 +4,7 @@
  *
  * @author Jakob Sack
  * @copyright 2011 Jakob Sack mail@jakobsack.de
+ * @copyright 2012 Thomas Tanghus (thomas@tanghus.net)
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -30,7 +31,14 @@ $id = isset($_POST['id'])?$_POST['id']:null;
 if(!$id) {
 	bailOut(OC_Contacts_App::$l10n->t('id is not set.'));
 }
-$card = OC_Contacts_App::getContactObject( $id );
 
-OC_Contacts_VCard::delete($id);
+try {
+	OC_Contacts_VCard::delete($id);
+} catch(Exception $e) {
+	$msg = $e->getMessage();
+	OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$msg,
+		OCP\Util::DEBUG);
+	OCP\Util::writeLog('contacts', __METHOD__.', id'.$id, OCP\Util::DEBUG);
+	bailOut($msg);
+}
 OCP\JSON::success(array('data' => array( 'id' => $id )));
diff --git a/apps/contacts/ajax/contact/deleteproperty.php b/apps/contacts/ajax/contact/deleteproperty.php
index b76eb19462..b76b6e55ed 100644
--- a/apps/contacts/ajax/contact/deleteproperty.php
+++ b/apps/contacts/ajax/contact/deleteproperty.php
@@ -40,8 +40,10 @@ if(is_null($line)) {
 
 unset($vcard->children[$line]);
 
-if(!OC_Contacts_VCard::edit($id, $vcard)) {
-	bailOut($l10n->t('Error deleting contact property.'));
+try {
+	OC_Contacts_VCard::edit($id, $vcard);
+} catch(Exception $e) {
+	bailOut($e->getMessage());
 }
 
 OCP\JSON::success(array(
diff --git a/apps/contacts/ajax/contact/list.php b/apps/contacts/ajax/contact/list.php
index c5eca292f1..4e2509d8d5 100644
--- a/apps/contacts/ajax/contact/list.php
+++ b/apps/contacts/ajax/contact/list.php
@@ -41,6 +41,10 @@ foreach($active_addressbooks as $addressbook) {
 				= array('contacts' => array('type' => 'book',));
 		$contacts_addressbook[$addressbook['id']]['displayname']
 				= $addressbook['displayname'];
+		$contacts_addressbook[$addressbook['id']]['permissions']
+				= isset($addressbook['permissions'])
+				? $addressbook['permissions']
+				: '0';
 	}
 }
 
@@ -75,10 +79,14 @@ if($contacts_alphabet) {
 			}
 		}
 		$contacts_addressbook[$contact['addressbookid']]['contacts'][] = array(
-					'type' => 'contact',
-					'id' => $contact['id'],
-					'addressbookid' => $contact['addressbookid'],
-					'displayname' => htmlspecialchars($display)
+			'type' => 'contact',
+			'id' => $contact['id'],
+			'addressbookid' => $contact['addressbookid'],
+			'displayname' => htmlspecialchars($display),
+			'permissions' =>
+				isset($contacts_addressbook[$contact['addressbookid']]['permissions'])
+					? $contacts_addressbook[$contact['addressbookid']]['permissions']
+					: '0',
 		);
 	}
 }
diff --git a/apps/contacts/ajax/contact/saveproperty.php b/apps/contacts/ajax/contact/saveproperty.php
index 296b9ad3b7..7ae183538b 100644
--- a/apps/contacts/ajax/contact/saveproperty.php
+++ b/apps/contacts/ajax/contact/saveproperty.php
@@ -162,9 +162,10 @@ if(!$value) {
 }
 //debug('New checksum: '.$checksum);
 
-if(!OC_Contacts_VCard::edit($id, $vcard)) {
-	bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.'));
-	exit();
+try {
+	OC_Contacts_VCard::edit($id, $vcard);
+} catch(Exception $e) {
+	bailOut($e->getMessage());
 }
 
 OCP\JSON::success(array('data' => array(
diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js
index a6262349df..09cf26bf7f 100644
--- a/apps/contacts/js/contacts.js
+++ b/apps/contacts/js/contacts.js
@@ -398,12 +398,32 @@ OC.Contacts={
 				localLoadContact(newid, bookid);
 			}
 		},
+		setEnabled:function(enabled) {
+			console.log('setEnabled', enabled);
+			$('.contacts_property,.action').each(function () {
+				$(this).prop('disabled', !enabled);
+				OC.Contacts.Card.enabled = enabled;
+			});
+		},
 		doExport:function() {
 			document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id;
 		},
 		editNew:function(){ // add a new contact
-			this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = '';
-			OC.Contacts.Card.add(';;;;;', '', '', true);
+			var book = $('#contacts h3.active');
+			var permissions = parseInt(book.data('permissions'));
+			if(permissions == 0
+					|| permissions & OC.Share.PERMISSION_UPDATE
+					|| permissions & OC.Share.PERMISSION_DELETE) {
+				with(this) {
+					delete id; delete fn; delete fullname; delete givname; delete famname;
+					delete addname; delete honpre; delete honsuf;
+				}
+				this.bookid = book.data('id');
+				OC.Contacts.Card.add(';;;;;', '', '', true);
+			} else {
+				OC.dialogs.alert(t('contacts', 'You do not have permission to add contacts to ')
+					+ book.text() + '. ' + t('contacts', 'Please select one of your own address books.'), t('contacts', 'Permission error'));
+			}
 			return false;
 		},
 		add:function(n, fn, aid, isnew){ // add a new contact
@@ -497,11 +517,16 @@ OC.Contacts={
 			OC.Contacts.notify({
 				data:curlistitem,
 				message:t('contacts','Click to undo deletion of "') + curlistitem.find('a').text() + '"',
-				timeout:5,
+				//timeout:5,
 				timeouthandler:function(contact) {
 					console.log('timeout');
-					OC.Contacts.Card.doDelete(contact.data('id'), true);
-					delete contact;
+					OC.Contacts.Card.doDelete(contact.data('id'), true, function(res) {
+						if(!res) {
+							OC.Contacts.Contacts.insertContact({contact:contact});
+						} else {
+							delete contact;
+						}
+					});
 				},
 				clickhandler:function(contact) {
 					OC.Contacts.Contacts.insertContact({contact:contact});
@@ -510,7 +535,7 @@ OC.Contacts={
 				}
 			});
 		},
-		doDelete:function(id, removeFromQueue) {
+		doDelete:function(id, removeFromQueue, cb) {
 			var updateQueue = function(id, remove) {
 				if(removeFromQueue) {
 					OC.Contacts.Contacts.deletionQueue.splice(OC.Contacts.Contacts.deletionQueue.indexOf(parseInt(id)), 1);
@@ -523,14 +548,23 @@ OC.Contacts={
 			if(OC.Contacts.Contacts.deletionQueue.indexOf(parseInt(id)) == -1 && removeFromQueue) {
 				console.log('returning');
 				updateQueue(id, removeFromQueue);
+				if(typeof cb == 'function') {
+					cb(true);
+				}
 				return;
 			}
-			$.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'),{'id':id},function(jsondata) {
+			$.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'), {'id':id},function(jsondata) {
 				if(jsondata.status == 'error'){
-					OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+					OC.Contacts.notify({message:jsondata.data.message});
+					if(typeof cb == 'function') {
+						cb(false);
+					}
 				}
 				updateQueue(id, removeFromQueue);
 			});
+			if(typeof cb == 'function') {
+				cb(true);
+			}
 		},
 		loadContact:function(jsondata, bookid){
 			this.data = jsondata;
@@ -563,6 +597,11 @@ OC.Contacts={
 				$('#contact_note').hide();
 				$('#contacts_propertymenu_dropdown a[data-type="NOTE"]').parent().show();
 			}
+			var permissions = OC.Contacts.Card.permissions = parseInt($('#contacts ul[data-id="' + bookid + '"]').data('permissions'));
+			console.log('permissions', permissions);
+			this.setEnabled(permissions == 0
+				|| permissions & OC.Share.PERMISSION_UPDATE
+				|| permissions & OC.Share.PERMISSION_DELETE);
 		},
 		loadSingleProperties:function() {
 			var props = ['BDAY', 'NICKNAME', 'ORG', 'URL', 'CATEGORIES'];
@@ -757,6 +796,13 @@ OC.Contacts={
 					console.log('Saving: ' + q);
 					$(obj).attr('disabled', 'disabled');
 					$.post(OC.filePath('contacts', 'ajax', 'contact/saveproperty.php'),q,function(jsondata){
+						if(!jsondata) {
+							OC.dialogs.alert(t('contacts', 'Unknown error. Please check logs.'), t('contacts', 'Error'));
+							OC.Contacts.loading(obj, false);
+							$(obj).removeAttr('disabled');
+							OC.Contacts.Card.update({cid:OC.Contacts.Card.id});
+							return false;
+						}
 						if(jsondata.status == 'success'){
 							container.data('checksum', jsondata.data.checksum);
 							OC.Contacts.Card.savePropertyInternal(name, fields, checksum, jsondata.data.checksum);
@@ -768,6 +814,7 @@ OC.Contacts={
 							OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
 							OC.Contacts.loading(obj, false);
 							$(obj).removeAttr('disabled');
+							OC.Contacts.Card.update({cid:OC.Contacts.Card.id});
 							return false;
 						}
 					},'json');
@@ -787,12 +834,16 @@ OC.Contacts={
 							OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
 							OC.Contacts.loading(obj, false);
 							$(obj).removeAttr('disabled');
+							OC.Contacts.Card.update({cid:OC.Contacts.Card.id});
 							return false;
 						}
 					},'json');
 			}
 		},
 		addProperty:function(type) {
+			if(!this.enabled) {
+				return;
+			}
 			switch (type) {
 				case 'NOTE':
 					$('#contacts_propertymenu_dropdown a[data-type="'+type+'"]').parent().hide();
@@ -836,6 +887,9 @@ OC.Contacts={
 		},
 		deleteProperty:function(obj, type) {
 			console.log('deleteProperty');
+			if(!this.enabled) {
+				return;
+			}
 			OC.Contacts.loading(obj, true);
 			var checksum = OC.Contacts.checksumFor(obj);
 			if(checksum) {
@@ -887,6 +941,9 @@ OC.Contacts={
 			}
 		},
 		editName:function() {
+			if(!this.enabled) {
+				return;
+			}
 			var params = {id: this.id};
 			/* Initialize the name edit dialog */
 			if($('#edit_name_dialog').dialog('isOpen') == true) {
@@ -922,6 +979,9 @@ OC.Contacts={
 			}
 		},
 		saveName:function(dlg) {
+			if(!this.enabled) {
+				return;
+			}
 			//console.log('saveName, id: ' + this.id);
 			var n = new Array($(dlg).find('#fam').val().strip_tags(),$(dlg).find('#giv').val().strip_tags(),$(dlg).find('#add').val().strip_tags(),$(dlg).find('#pre').val().strip_tags(),$(dlg).find('#suf').val().strip_tags());
 			this.famname = n[0];
@@ -1010,6 +1070,9 @@ OC.Contacts={
 			return false;
 		},
 		editAddress:function(obj, isnew){
+			if(!this.enabled) {
+				return;
+			}
 			var container = undefined;
 			var params = {id: this.id};
 			if(obj === 'new') {
@@ -1135,6 +1198,9 @@ OC.Contacts={
 			}
 		},
 		saveAddress:function(dlg, obj, isnew){
+			if(!this.enabled) {
+				return;
+			}
 			if(isnew) {
 				container = $('#addresses dl').last();
 				obj = container.find('input').first();
@@ -1177,6 +1243,9 @@ OC.Contacts={
 			container.find('.addresslist').html(adrtxt);
 		},
 		uploadPhoto:function(filelist) {
+			if(!this.enabled) {
+				return;
+			}
 			if(!filelist) {
 				OC.dialogs.alert(t('contacts','No files selected for upload.'), t('contacts', 'Error'));
 				return;
@@ -1255,6 +1324,9 @@ OC.Contacts={
 			this.loadPhotoHandlers()
 		},
 		editCurrentPhoto:function(){
+			if(!this.enabled) {
+				return;
+			}
 			$.getJSON(OC.filePath('contacts', 'ajax', 'currentphoto.php'),{'id':this.id},function(jsondata){
 				if(jsondata.status == 'success'){
 					//alert(jsondata.data.page);
@@ -1268,6 +1340,9 @@ OC.Contacts={
 			});
 		},
 		editPhoto:function(id, tmpkey){
+			if(!this.enabled) {
+				return;
+			}
 			//alert('editPhoto: ' + tmpkey);
 			$.getJSON(OC.filePath('contacts', 'ajax', 'cropphoto.php'),{'tmpkey':tmpkey,'id':this.id, 'requesttoken':requesttoken},function(jsondata){
 				if(jsondata.status == 'success'){
@@ -1284,7 +1359,10 @@ OC.Contacts={
 				$('#edit_photo_dialog').dialog('open');
 			}
 		},
-		savePhoto:function(){
+		savePhoto:function() {
+			if(!this.enabled) {
+				return;
+			}
 			var target = $('#crop_target');
 			var form = $('#cropform');
 			var wrapper = $('#contacts_details_photo_wrapper');
@@ -1719,11 +1797,15 @@ OC.Contacts={
 							firstrun = true;
 							if($('#contacts h3').length == 0) {
 								$('#contacts').html('<h3 class="addressbook" contextmenu="addressbookmenu" data-id="'
-									+ b+'">'+book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>');
+									+ b + '" data-permissions="' + book.permissions + '">' + book.displayname
+									+ '</h3><ul class="contacts hidden" data-id="'+b+'" data-permissions="'
+									+ book.permissions + '"></ul>');
 							} else {
-								if(!$('#contacts h3[data-id="'+b+'"]').length) {
-									var item = $('<h3 class="addressbook" contextmenu="addressbookmenu" data-id="'+b+'">'
-										+ book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>')
+								if(!$('#contacts h3[data-id="' + b + '"]').length) {
+									var item = $('<h3 class="addressbook" contextmenu="addressbookmenu" data-id="'
+										+ b + '" data-permissions="' + book.permissions + '">'
+										+ book.displayname+'</h3><ul class="contacts hidden" data-id="' + b
+										+ '" data-permissions="' + book.permissions + '"></ul>');
 									var added = false;
 									$('#contacts h3').each(function(){
 										if ($(this).text().toLowerCase() > book.displayname.toLowerCase()) {
diff --git a/apps/contacts/lib/share/addressbook.php b/apps/contacts/lib/share/addressbook.php
index 0c90470d37..d35f0ce9ae 100644
--- a/apps/contacts/lib/share/addressbook.php
+++ b/apps/contacts/lib/share/addressbook.php
@@ -73,6 +73,7 @@ class OC_Share_Backend_Addressbook implements OCP\Share_Backend_Collection {
 				$addressbook = OC_Contacts_Addressbook::find($item['item_source']);
 				if ($addressbook) {
 					$addressbook['displayname'] = $item['item_target'];
+					$addressbook['permissions'] = $item['permissions'];
 					$addressbooks[] = $addressbook;
 				}
 			}
diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php
index 58d78875af..cee9c640cf 100644
--- a/apps/contacts/lib/vcard.php
+++ b/apps/contacts/lib/vcard.php
@@ -405,7 +405,7 @@ class OC_Contacts_VCard{
 		if ($addressbook['userid'] != OCP\User::getUser()) {
 			$sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
 			if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_UPDATE)) {
-				return false;
+				throw new Exception(OC_Contacts_App::$l10n->t('You do not have the permissions to edit this contact.'));
 			}
 		}
 		OC_Contacts_App::loadCategoriesFromVCard($card);
@@ -423,7 +423,8 @@ class OC_Contacts_VCard{
 		try {
 			$result = $stmt->execute(array($fn,$data,time(),$id));
 		} catch(Exception $e) {
-			OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+			OCP\Util::writeLog('contacts', __METHOD__.', exception: '
+				. $e->getMessage(), OCP\Util::ERROR);
 			OCP\Util::writeLog('contacts', __METHOD__.', id'.$id, OCP\Util::DEBUG);
 			return false;
 		}
@@ -444,10 +445,21 @@ class OC_Contacts_VCard{
 		$oldcard = self::findWhereDAVDataIs($aid, $uri);
 		$card = OC_VObject::parse($data);
 		if(!$card) {
-			OCP\Util::writeLog('contacts', __METHOD__.', Unable to parse VCARD, uri: '.$uri, OCP\Util::ERROR);
+			OCP\Util::writeLog('contacts', __METHOD__.
+				', Unable to parse VCARD, uri: '.$uri, OCP\Util::ERROR);
+			return false;
+		}
+		try {
+			self::edit($oldcard['id'], $card);
+			return true;
+		} catch(Exception $e) {
+			OCP\Util::writeLog('contacts', __METHOD__.', exception: '
+				. $e->getMessage() . ', '
+				. OCP\USER::getUser(), OCP\Util::ERROR);
+			OCP\Util::writeLog('contacts', __METHOD__.', uri'
+				. $uri, OCP\Util::DEBUG);
 			return false;
 		}
-		return self::edit($oldcard['id'], $card);
 	}
 
 	/**
@@ -462,18 +474,28 @@ class OC_Contacts_VCard{
 		}
 		$addressbook = OC_Contacts_Addressbook::find($card['addressbookid']);
 		if ($addressbook['userid'] != OCP\User::getUser()) {
-			$sharedContact = OCP\Share::getItemSharedWithBySource('contact', $id, OCP\Share::FORMAT_NONE, null, true);
-			if (!$sharedContact || !($sharedContact['permissions'] & OCP\Share::PERMISSION_DELETE)) {
-				return false;
-			}
-		}
-		OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard', array('aid' => null, 'id' => $id, 'uri' => null));
-		$stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE id = ?' );
+			$sharedContact = OCP\Share::getItemSharedWithBySource('contact',
+				$id, OCP\Share::FORMAT_NONE, null, true);
+			if (!$sharedContact
+				|| !($sharedContact['permissions'] & OCP\Share::PERMISSION_DELETE)) {
+				throw new Exception(
+					OC_Contacts_App::$l10n->t(
+						'You do not have the permissions to delete this contact.'
+					)
+				);
+			}
+		}
+		OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard',
+			array('aid' => null, 'id' => $id, 'uri' => null)
+		);
+		$stmt = OCP\DB::prepare('DELETE FROM *PREFIX*contacts_cards WHERE id = ?');
 		try {
 			$stmt->execute(array($id));
 		} catch(Exception $e) {
-			OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
-			OCP\Util::writeLog('contacts', __METHOD__.', id: '.$id, OCP\Util::DEBUG);
+			OCP\Util::writeLog('contacts', __METHOD__.
+				', exception: ' . $e->getMessage(), OCP\Util::ERROR);
+			OCP\Util::writeLog('contacts', __METHOD__.', id: '
+				. $id, OCP\Util::DEBUG);
 			return false;
 		}
 
-- 
GitLab