-1

I have a google sheets document with two tabs one called called internal and the other called external. How can i hide the internal tab from other users? the lock function already avialble is not good enough I only want people from my company to be able to see both tabs, clients should only be able to see the external tab.

function validUsers() { 
String[] adminUsers = {”email1@gmail.com”,”email2@gmail.com”,”email3@gmail.com”};

if (adminUsers.indexOf(Session.getEffectiveUser().getEmail()) >= 0) { 
    SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').showSheet() 
else
    SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').hideSheet() 
} 

}

  • 1
    You cannot hide tabs for only some users. I'd consider creating an [Apps Script web app](https://developers.google.com/apps-script/guides/web) or similar in order to achieve the functionality you desired, but first it would be necessary for you to provide more information on your current situation, including providing a copy of the spreadsheet you're working on, free of sensitive information. – Iamblichus Sep 16 '20 at 07:43
  • This is the document https://docs.google.com/spreadsheets/d/14alf6zCUAAYClk5w-FMoEal9jv1k2OaCBl6QjaE1nsU/edit#gid=0 not anything it as not really needed but it shows how my real document is set up. I just added the current script I have to my main post – stacksonoverflow Sep 16 '20 at 14:27
  • The problem is that even if you add an `onOpen` trigger so that a sheet gets hidden if the file is opened by an external user, there could be a company user opening the spreadsheet at the time, and for this user, the sheet should be unhidden. But you cannot have a sheet hidden and unhidden at the same time. All users will see the same. If the spreadsheet was used by each user at a time (not more than one at the same time), this would be feasible. But I guess that's not the case, right? In this case, I think a workaround like the one proposed by a-burge would be your best option. – Iamblichus Sep 17 '20 at 12:52
  • It depends really but for most of the time external and internal users are not viewing this at the same time although it does happen occasionally – stacksonoverflow Sep 17 '20 at 20:17
  • So in this case, would the workaround I suggested be appropriate? If that's the case, I'd post an answer explaining it. – Iamblichus Sep 18 '20 at 06:59
  • please do if you can Iamblichus – stacksonoverflow Sep 19 '20 at 20:56
  • Do you have a list of users who should be able to see the tabs (like the `adminUsers` in the sample code you shared)? – Iamblichus Sep 21 '20 at 08:50
  • I do but for now it's not needed, just use dummy emails – stacksonoverflow Sep 21 '20 at 09:21
  • The feasibility of this workaround would depend on the script to know which accounts are supposed to be able to see this. If you don't define an array or similar with these emails, this cannot work. – Iamblichus Sep 21 '20 at 09:31
  • Sorry, I was not asking for the specific email addresses, but to whether an array with those addresses would be available to the script, so that the script can check the user accessing the spreadsheet is one of the admins (the ones who can see those tabs). Also, another important question: are all users accessing this spreadsheet part of the same G Suite domain? Or at least, are the admin users part of the same G Suite domain? (if the latter is not the case, this cannot work). – Iamblichus Sep 21 '20 at 14:17
  • yes and array of those address would be available to the script. Yes all the admin users are part of same g suite domain. – stacksonoverflow Sep 21 '20 at 16:43
  • Related [Hiding tabs/sheets in Google Spreadsheet from certain users](https://stackoverflow.com/q/50172217/1595451), [Hiding worksheets from specific users inside a spreadsheet](https://stackoverflow.com/q/62048754/1595451), [Show sheets depending on type of user](https://stackoverflow.com/q/22368964/1595451) – Rubén Sep 22 '20 at 14:27

2 Answers2

2

Issue:

  • You want to hide or show a sheet in your spreadsheet depending on which user is accessing the spreadsheet.

Solution:

You could do the following:

  • Install an onOpen trigger which executes a function (let's call it fireOnOpen) every time a user opens the spreadsheet.
  • The function fireOnOpen should check which user is accessing the spreadsheet, and hide or show a certain sheet (called Internal) depending on this.
  • In order to check the current user accessing the spreadsheet, you can use getActiveUser() (instead of getEffectiveUser(), which will return the user who installed the trigger).

Workflow:

  • The trigger can be installed either manually or programmatically. To do it programmatically, copy this function to your script editor and execute it once:
function createOnOpenTrigger() {
  var ss = SpreadsheetApp.getActive();
  ScriptApp.newTrigger("fireOnOpen")
  .forSpreadsheet(ss)
  .onOpen()
  .create();
}
  • This will result in fireOnOpen being executed every time a user accessed the spreadsheet. The fireOnOpen function could be something like this:
function fireOnOpen() { 
  const adminUsers = ["email1@gmail.com","email2@gmail.com","email3@gmail.com"];
  const currentUser = Session.getActiveUser().getEmail();
  const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
  if (adminUsers.includes(currentUser)) internalSheet.showSheet();
  else internalSheet.hideSheet();
} 

Important notes:

  • You cannot hide sheets for some users but not for others. A hidden sheet is hidden for all users, and a visible sheet is visible for all users. Therefore, this will only work if internal and external users don't access the spreadsheet at the same time. If they do, external users might be able to access the Internal sheet.
  • getActiveUser() is not always populated, as you can see on this answer, so please make sure that all admin users are from the same G Suite domain. Otherwise, this won't work.
  • If the privacy of the Internal sheet is critical and there is a possibility of internal and external users accessing the spreadsheet at the time, I would not recommend this solution.

Edit:

As mentioned in comments, a possible workaround for the occasions when admin and non-admin users access the file at the time could be the following:

  • When an admin user accesses the file, store the time in which that happened.
  • Create a time-driven trigger to execute a function periodically (every 5 minutes, let's say), which will check if an admin accessed the file a short time ago (let's say 30 minutes). If the admin has done that, remove the Permissions for the different non-admin domains. If that's not the case, add these Permissions back.
  • Enabling the Drive Advanced Service would be required in this case.

Updated code sample:

function fireOnOpen() { 
  const adminUsers = ["email1@gmail.com","email2@gmail.com","email3@gmail.com"];
  const currentUser = Session.getActiveUser().getEmail();
  const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
  if (adminUsers.includes(currentUser)) {
    internalSheet.showSheet();    
    const documentProperties = PropertiesService.getDocumentProperties();
    documentProperties.setProperty("lastAdminAccess", new Date().getTime()); // Store time of admin access
  } else internalSheet.hideSheet();
} 

function createOnOpenTrigger() {
  var ss = SpreadsheetApp.getActive();
  ScriptApp.newTrigger("fireOnOpen")
  .forSpreadsheet(ss)
  .onOpen()
  .create();
}

function updatePermissions() {
  const fileId = SpreadsheetApp.getActive().getId();
  const lastAdminAccess = PropertiesService.getDocumentProperties().getProperty("lastAdminAccess"); // Last time of admin access in ms
  const now = new Date().getTime(); // Current time in milliseconds
  const thirtyMinutes = 1000 * 60 * 30; // 30 minutes in milliseconds
  if (now - lastAdminAccess < thirtyMinutes) {
    const currentPermissions = Drive.Permissions.list(fileId)["items"];
    const publicPermissionIds = currentPermissions.filter(permission => permission["type"] === "anyone")
                                                  .map(permission => permission["id"]);
    publicPermissionIds.forEach(permissionId => Drive.Permissions.remove(fileId, permissionId));
  } else {
    const resource = {
      type: "anyone",
      role: "reader"
    }      
    Drive.Permissions.insert(resource, fileId);
  }
}

function createTimeTrigger() {
  ScriptApp.newTrigger("updatePermissions")
  .timeBased()
  .everyMinutes(5)
  .create();
}
Iamblichus
  • 18,540
  • 2
  • 11
  • 27
  • I just added a comment with links to similar questions. Before marking this Q as duplicate of one of them as you was having a discussion witht the OP I would like to know what you think, Is this a duplicate of one of them? – Rubén Sep 22 '20 at 14:32
  • 1
    @Rubén Feel free to mark it as duplicate. – Iamblichus Sep 23 '20 at 10:26
  • Thanks for the solution it works. I think i need to find out way now to kick people out of the spreadsheet after x minutes just so incase an admin user has the tab open and has forgotten while a non admin user is viewing the document – stacksonoverflow Sep 23 '20 at 13:56
  • @stacksonoverflow I can see several problems with that approach: I guess "kicking people out" means unsharing the file with them. What action should trigger that? And when should the access be given again? I'm not sure exploring this way can provide any good results. – Iamblichus Sep 24 '20 at 11:06
  • I just accepted your answer, thanks again for the help. Well yes kind of kicking people out after x minute no real action should trigger it they should have access if they just open the link again if that makes sense. It's very rare that an admin and non-admin user are viewing the document at the same time but if it happens there needs to be some kind of timeout so the non-admin user can't see the internal sheet. – stacksonoverflow Sep 24 '20 at 13:33
  • @stacksonoverflow The main problem (or one of the main ones) I see with this approach is that time-based triggers don't depend on a user making a certain action, so you cannot use `getActiveUser()` to check whether the user is an admin and unshare the document with this user. Another option would be to make the `onOpen` function register each time an admin accesses the file, and unshare the document with all non-admins every time this happens. And after some time of no admins accessing the file, share the file again with the non-admins via time-based trigger. – Iamblichus Sep 24 '20 at 13:42
  • @stacksonoverflow This last suggested workaround, though, would require having a list of non-admin users. Can the script have this list? – Iamblichus Sep 24 '20 at 13:48
  • it can but not sure if it be feasible because the non-admin users would be clients and I have many from different companies – stacksonoverflow Sep 25 '20 at 16:53
  • @stacksonoverflow Maybe a list of domains corresponding to the non-admin users? Would that be a possible thing to have? (a file can be shared with all accounts from a domain). – Iamblichus Sep 29 '20 at 08:39
  • yes that would work – stacksonoverflow Sep 29 '20 at 15:37
  • @stacksonoverflow I have updated my answer with the suggested workaround. I hope this is helpful to you. – Iamblichus Oct 01 '20 at 11:19
  • Thanks @Iamblichus I have tried the new script with no success, I enabled the Drive Advanced Service and changed the time of const thirtyMinutes to 1 minute for testing purposes and also the number of everyMinutes to 1 minute. When running the script now nothing happens, I added @ gmail to the domain of non-domains. Also what would happen if the document is viewied by someone who is anonymous and the document has the anyone with the link can view feature enabled – stacksonoverflow Oct 02 '20 at 12:22
  • @stacksonoverflow I don't think `gmail` is a valid domain. About your last question. Do you mean sharing the document with "Anyone with the link" instead of having a set of non-admin domains to share with? It would be ok to share the file with anyone? If that's the case, that could certainly be an option. – Iamblichus Oct 05 '20 at 14:17
  • Yes exactly so the document when shared is usually shared with people using the sharing feature "Anyone with the link" . Is it still possible to restrict people using the script if the document is shared this way? – stacksonoverflow Oct 05 '20 at 16:23
  • @stacksonoverflow If they have edit access to the spreadsheet, they'll be able to access and use the script. Do they need to have edit access, or just view? – Iamblichus Oct 06 '20 at 14:36
  • @Iamblichus they will just need to view the sheet – stacksonoverflow Oct 06 '20 at 21:16
  • @stacksonoverflow I updated my answer so that the Permission that is continuously created and removed (depending on whether admins are accessing the spreadsheet) is with `Anyone with the link`, not with certain domains, and with `reader` access. – Iamblichus Oct 07 '20 at 13:55
1

As soon as you share a sheet you should assume that anyone can see the data in it. Even if someone shouldn't be able to see the internal tab, they can always e.g. make a copy of the sheet and thus get to the data.

You could try creating a separate sheet and using =IMPORTRANGE() to refer to the original one. But know that once you allow the connection between the two sheets, anyone with access to the second one might be able to access anything in the first one. Maybe get around that using three sheets:

  1. Internal + External - your current sheet

  2. A sheet-in-the-middle that only you can access. It has a single tab Internal that uses =IMPORTRANGE() to access data from 1)

  3. The External sheet for clients. Linked to 2) through =IMPORTRANGE()

This way 3) only has access to the data in 2) which in turn only includes a link to 1).

I do not promise that this will make the data safe from those who shouldn't see it. But it will at least be safer.

a-burge
  • 1,535
  • 1
  • 13
  • 25
  • ill i will try this out and see how it works. I think i need some type of script to get it working how i described – stacksonoverflow Sep 15 '20 at 21:50
  • So I had time to think about this and do some research, this wont method wouldn't be suitable for me because the document I posted has to be duplicated everytime it's used because its basically a template. If i go with what you suggested i would need to find somehow to also duplicate the current sheet you referred to if that makes sense? – stacksonoverflow Sep 17 '20 at 22:07
  • How about keeping this one sheet as master and archiving copies of it as time progresses? – a-burge Sep 18 '20 at 08:28