Sharing access to partner community users based on record owner

Update 03-11-16

Two important revisions to communities have occurred since I wrote this answer that should be considered. First is that User Sharing and visibility restrictions on the User Object in an Org are now in place (SU 15 as I recall). This impacts SF Users visibility to other users both inside an Org and within an Org's Customer Community.

The 2nd one is the introduction of Customer Community Plus licenses. These licenses DO NOT have the same sharing model as regular Customer Communities. That sharing is vastly different. However, Customer Community Plus licenses support Managed Apex Sharing which is often the only way in which records can be shared. Sharing groups are not available as they are with regular Customer Communities. Please keep this in mind when reading this answer.

EDIT 3

I just found this: "You can share a standard or custom object with users or groups. Apex sharing is not available for Customer Community users." on the following page http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_bulk_sharing_creating_with_apex.htm. I think that's the definitive answer regarding Apex Sharing with Community Users. You'll need to either change owners or set up a sharing group.

EDIT 2

I'd gotten that the customer wasn't a contact, but that the Portal User was. You still need to get the Owner of the Portal User.contact to set up your sharing if you're going to do it by User. Remember: Portal Users do not have a Role. You're not going to be able to do any kind of role based sharing unless you do it as a child of the Owner.

Your idea of transferring ownership to the system admin has a lot of merit, especially if the Portal User doesn't need to retain ownership and only needs to be able to view the record. That would likely be the simplest solution to your problem.

EDIT 1

I've edited my post to included some of my comments below and try to clarify them rather than create a long back and forth thread.

Fist, depending on your actual schema, I believe the above query should retrieve the Owner's ID and UserRoleId, not the Portal User's RoleId provided the contact__c object is the same as the contact object.

Since both Owners who have SF Licenses and Portal Users that have Communities licenses are all Users, in theory, you could get them all in one query. You can also do them as two separate queries. How best to do them depends on what info you need and whether one depends on the other. Its also a matter of what method you're most comfortable with.

The Owner(a SF User) owns the Contact and the Account associated with the Contact, but not the Communities User. To be clear, a Communities User cannot own another Contact nor an Account simply because they don't have anything other than read access to those objects for their own information (both contact and account)!

However any kind of User can be retrieved via a query on User. Additionally, a User's Contact info can be retrieved by a query on User (contact = User.contact.Id, contact.Name = User.contact.Name, etc.).

You say below that the Owner of the contact for the Portal User is also a Portal User, the question I'd have is what license that Portal User has? It can't be a Communities License.

If this helps, here's how Communities Users are created using Apex:

Account> a = new Account(Name = Acme ); 
/* OwnerID will either be specified or will be ID of User who performs this operation.*/
insert a;

Contact c = new Contact(FirstName = John, LastName = Doe, Account = a[0].Id (ID of Ajax Acct), email = [email protected], Phone = 800-555-1212, MailingStreet = .....); 
/* you know the drill for the rest of the data required. */
/* Again, the OwnerID is for the User who performs the operation unless an OwnerID is specified */
insert c;

User u =new User(ContactID = c[0].Id (John Doe's contact.Id), Alias = 'PrtlUsr1', Email= c[0].Email, FirstName= c[0].FirstName, LastName= c[0].LastName, userName=c[0].Email, ProfileID = CommunitiesLicenceID for your org);
/* Again, the OwnerID is for the User who performs the operation unless an OwnerID is specified */
Insert u;

In my experience, it requires a SF User license to create a Portal User.

The owner of the customer is a portal user tho, i guess thats what im not understanding. Are you saying is should be getting the portal user -> Contact -> and that owner? how does that help when what im trying to share is the "customer" I think people are getting confused because they are called "customers" but they are just a custom object.

This could very well be the "disconnect" that's been occurring. Since there is no "role hierarchy", yes, you need to be retrieving the Portal User Contact and the Owner of the Portal User Contact in order to share anything that belongs to the Portal User. Why? Because the only other person that can see everything the Portal User "owns" is the Owner of the Portal User Contact -> (Portal) User.contact.OwnerID!

The only other way that immediately comes to mind that might help you accomplish what you desire would be to utilize your trigger to add these Users to a Sharing Group of some kind; something you might also want to consider as an alternative.

I hope this provides the clarification you needed in order to help you move forward with your trigger.

BTW, when you do your test class, I recommend you utilize "Run As" when you create your Account, Contact and Portal User. That will automatically create the Portal User with the Run As User as it's Owner.

Original Answer

Communities Users have an Owner. They do not have a Role, all they have is a User Profile. Essentially, they quasi inherit their Role from their Owner. Their Owner can see any of the records they create. So, if you want to share something a Communities User "owns", you're going to need to do that through their Owner.

trigger shareCustomer on Customer__c (after insert, after update) {

   List<Customer__Share> customerShares  = new List<Customer__Share>();
   Customer__Share customerShare;

I agree with Greenstork, you need the User Role Id from related Owners (Users) in the trigger set. Now the question is:

Are your customers logging in as Communities Users or as Customers (Contacts)?

If as Users, then your query may need to reflect that. Your query currently shows as Customer__c, which is a Custom Object. Is there a relationship between Customer__c and either Contacts or Communities Users?

List<Customer__c> enhancedCustomerList = [SELECT id, Owner.UserRoleId, OwnerID FROM Customer__c WHERE id IN :trigger.new];

If there is, you may want to utilize that relationship in your trigger. That's for you to decide. I primarily say that because it may simplify things for you because of Ownership when it comes to Communities Users.

The rest of your trigger would look pretty much the same as what Greenstork has shown depending on the relationships you use between contact, account, Portal User, and Portal User.OwnerID. The sharing is going to occur based on the Owner of contact/customer__c/Portal User.


EDIT: This answer was given prior to the OP making it clear that the owner of the Customer__c records was a customer community user and not a partner community user.

In the case of partner community users, the role of the record owner is shared by all users with the same account. Instead of trying to create share records for each user, consider just sharing to that role, which is unique by all accounts. That is to say, there is no danger of sharing across accounts since roles are unique for each partner.

Also, as a side note, your code is far from bulk safe. You have SOQL statements inside two for loops and this could lead to big problems with bulk DML operations on the Customer__c object.

Try this instead:

trigger shareCustomer on Customer__c (after insert, after update) {

    List<Customer__Share> customerShares  = new List<Customer__Share>();
    Customer__Share customerShare;

    //you need the User Role Id from related owners (users) in the trigger set
    List<Customer__c> enhancedCustomerList = [SELECT id, Owner.UserRoleId FROM Customer__c WHERE id IN :trigger.new];

    for(Customer__c customer : enhancedCustomerList){

        customerShare = new Customer__Share();
        customerShare.ParentId = customer.Id;
        customerShare.UserOrGroupId = customer.Owner.UserRoleId;
        customerShare.AccessLevel = 'edit';

        customerShares.add(customerShare);
    }

    try {
        Database.SaveResult[] lsr = Database.insert(customerShares,false);
    } catch (Exception e) {
        //handle your exception
    }

}

What's not included here and you might consider is some cleanup code to remove shares when record ownership changes. I also don't remember what happens when you try to create a share record when it already exists.