[SalesForce] User with a read only profile updates records

I have an old test created by my predecessor which is checking that a user with a read only profile should fail to update records. After I made changes to my project, this test fails. The user with a read only profile succeeds in updating records. And I cannot deploy my changes to production because of this failure. I checked the code, profile, and debug logs and I do not understand how it is possible. Does anyone have any idea ?

EDIT 1
This test is using the following command in the code:

System.runAs(u)

Does anyone know if System.runAs(u) changed and now ignore all user permissions and profile during the last winter release?

Here are the profile image, code, and debug logs:

Read only profile:

enter image description here

Test code:

 static testMethod void dmlErroTest()
{
    String rTypeId;
    //get the faculty record type for contact
    List<RecordType> rTypes = [Select SobjectType, Name, Id From RecordType  Where SobjectType = 'Contact' AND Name = 'Faculty'];
    if(rTypes.size() < 1)
    {
        System.assertEquals(false, true);
    }
    else
    {
        rTypeId = rTypes[0].Id;
    }
    //create the schedule
    Academic_Schedule__c schedule = new Academic_Schedule__c(Name = '2008-2009');
    insert schedule;
    //create a faculty contact
    Contact faculty = new Contact(
                                    LastName='Doe', 
                                    RecordTypeId = rTypeId
                                  );
    insert faculty;

    //create courses
    List<Course__c> crs = new List<Course__c>();
    for(integer i = 0; i < 5; i++)
    {
        Course__c c;
        if( math.mod(i , 2) == 0)
        {
             c = new Course__c(Academic_Schedule__c = schedule.Id, Section_Number__c = 'D '+i, Faculty__c = null, Unmatched_Faculty_Name__c = 'Doe, John', Faculty_USC_Id__c = '9999999999');
        }
        else   
        {           
            c = new Course__c(Academic_Schedule__c = schedule.Id, Section_Number__c = 'D '+i, Faculty__c = null, Unmatched_Faculty_Name__c = 'Smith, Jane', Faculty_USC_Id__c = '8888888888');
        }           

        crs.add(c);
    }
    insert crs;
    for(Course__c c : [SELECT Id, Unmatched_Faculty_Name__c, Faculty_USC_Id__c, Faculty__c
                                FROM Course__c 
                                WHERE Unmatched_Faculty_Name__c = 'Doe, John'])
    {
        System.assertEquals(c.Faculty_USC_Id__c, '9999999999');
        System.assertEquals(c.Faculty__c, null);
    }

    PageReference pageRef = Page.MassUpdateUnmatchedFaculty;
    Test.setCurrentPage(pageRef);
    FacultyMatch matcher = new FacultyMatch();
    System.assert(matcher.Course.Id == null);
    System.assert(matcher.Courses.size() > 3);

    ApexPages.currentPage().getParameters().put('id', crs[0].Id);
    matcher = new FacultyMatch();
    System.assert(matcher.Courses.size() == 3);     
    System.assert(matcher.Course.Id == crs[0].Id);
    System.assert(matcher.Course.Id != null);
    System.assert(matcher.searchText == 'Doe, John');
    for(Course__c c : matcher.Courses)
    {
        System.assertEquals(c.Faculty_USC_Id__c, '9999999999');
        System.assertEquals(c.Unmatched_Faculty_Name__c, 'Doe, John');
        System.assertEquals(c.Faculty__c, null);
    }
    //set the controlling course faculty id before update call doUpdate
    matcher.Course.Faculty__c = faculty.Id;

    // This code runs as the readonly user
    Profile p = [SELECT Id FROM Profile WHERE Name='Read Only']; 

    System.debug('profile  = ' + p);

    User u = new User(  Alias = 'standt', Email='standarduser@testorg.com',
                        EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
                        LocaleSidKey='en_US', ProfileId = p.Id,
                        TimeZoneSidKey='America/Los_Angeles', UserName='stduser@testorg.com');
    System.Assert(ApexPages.hasMessages(ApexPages.Severity.ERROR) == false);

    System.runAs(u)
    {
        matcher.doUpdate();
    }

    System.debug('User = ' + u);        
    System.debug('ApexPages message: ' + Apexpages.getMessages().get(0).getSummary() );
    System.debug('ApexPages.hasMessages(ApexPages.Severity.ERROR) = ' + ApexPages.hasMessages(ApexPages.Severity.ERROR) );

    System.Assert(ApexPages.hasMessages(ApexPages.Severity.ERROR) == true);
    System.Assert(Apexpages.getMessages().get(0).getSummary().contains('Insufficient Privileges')== true);
}

Function tested in code

    public PageReference doUpdate() 
{
    try
    {
        if(course.Unmatched_Faculty_Name__c != null && course.Faculty__c != null && course.Faculty_USC_Id__c != null)
        {
            Contact c = new Contact(Id = course.Faculty__c, Faculty_USC_Id__c = course.Faculty_USC_Id__c);
            update c;

            //debug
            UserRecordAccess ac = [SELECT RecordId, HasEditAccess
                                   FROM UserRecordAccess
                                   WHERE UserId = :UserInfo.getUserId()
                                   AND RecordId = :c.Id ];

            System.debug('after updating Contact, UserRecordAccess = ' + ac);

            for(Course__c crs : Courses ) 
            {
                crs.Faculty__c = course.Faculty__c;
                crs.Faculty_USC_Id__c = null;
                crs.Unmatched_Faculty_Name__c = null;

                //debug
                 ac = [SELECT RecordId, HasEditAccess
                                   FROM UserRecordAccess
                                   WHERE UserId = :UserInfo.getUserId()
                                   AND RecordId = :crs.Id ];
            System.debug('Before updating courses, course = ' + crs + ' UserRecordAccess = ' + ac );

            }
            update Courses;   
            //debug

            System.debug('After updating courses');

            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, 'The faculty record was updated with the usc id '+ c.Faculty_USC_Id__c ));
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, Courses.size()+ ' courses were updated with the selected faculty'));
            doClear();
            return null;
        }
        else
        {
            throw new FacultyMatchException('Please ensure that you select and unmatched faculty name and a faculty to which the records should be matched.');
        }
    }
    catch(DmlException ex)
    {
        ApexPages.addMessages(ex);
    }
    catch(FacultyMatchException e)
    {
        ApexPages.addMessages(e);
    }
    return null;
}

Debug logs after test run : link

Best Answer

A complete shot in the dark, but I would guess someone has created a new Profile in production called 'Read Only' that is being returned before the default system profile.

E.g. You can create a new Profile with the same name as the system 'Read Only' Profile. Custom Profile with the same name as a system profile

To tell them apart you would need to use the Organization.CreatedDate to find the System created on. E.g.

Datetime orgCreatedDate = [Select id, CreatedDate from Organization limit 1].CreatedDate; 
List<Profile> readOnlyProfiles = [select id, Name from Profile where Name = 'Read Only' and CreatedDate = :orgCreatedDate];
// Maybe to a sanity check here to make sure there was only one matching Profile.

From the log, it appears that this line in the test case is causing the failure.

System.Assert(ApexPages.hasMessages(ApexPages.Severity.ERROR) == true);

For this assertion to pass the code would need to call ApexPages.addMessages(ex) (where ex is an exception).

I see the check against UserRecordAccess.HasEditAccess for the Contact comes back as false. Same with the HasEditAccess values for all the Course__c records.

So it does seem really odd that you can then turn round and update those records from an apex update. Assuming you have with sharing on, the next thing that comes to mind is the "Modify All Data" Administrative Permissions. However, I don't think you can change that on the built in Read Only role.

It doesn't appear to be the case, but it is worth confirming the the active user isn't the owner of the record you are updating.

"The record owner is automatically granted Full Access, allowing them to view, edit, transfer, share, and delete the record." Understanding Sharing

Related Topic