├── .gitignore ├── Admin ├── getSumOfDataInAccounts.py ├── splitTeam.py └── wipe_external_ids.py ├── AuditLog ├── GetEvents.ps1 └── GetMemberJoins.py ├── CSV_PDF_Migration_Script.py ├── Files ├── FileSizeQuotaAlert.py ├── GetFolderStats.py ├── ListTeamFiles.py ├── MoveData.py ├── QuotaAlert.py ├── SearchTeamFiles.py └── TeamUsageStatistics.py ├── Groups ├── ListGroupFolderPermissions.py ├── createGroupWithAllTeamMembers.py ├── createGroupsFromCSV.py └── getGroupMembers.py ├── Integrations ├── ListDeviceSessions.py ├── ListTeamApps.py └── listMembersLinkedApps.py ├── LICENSE ├── Paper ├── convertWordToPaper.py └── paper-export.py ├── QA Installer ├── Client-Event-Codes.md ├── Dropbox Enterprise Installer QA.ps1 ├── Dropbox Enterprise Installer.ps1 ├── QA-Event-Codes.md └── README.md ├── README.md ├── Sharing ├── ExternallySharedUsers.py ├── ListSharedFolderMembers.py ├── ListSharedFolders-pom.xml ├── ListSharedFolders.java ├── ListSharedLinks.py ├── dbx_email_alerts.py ├── getTeamMembersSharedLinks.py ├── list_delete_public_dbx_links.py ├── reports │ ├── Classes.py │ ├── getCollaborationReport-py3.py │ └── getCollaborationReport.py └── sharingAPI.py ├── Users ├── DeprovisionFromCsv.py ├── GetUserList.py ├── ListMembers.ps1 ├── ListMembers.py ├── ListPendingMembersInvitedBeforeDate.py ├── ProvisionFromCSV.py ├── RemindPendingMembers.py ├── SetMemberSpaceLimits.py ├── SuspendUsers.ps1 └── updateEmails.py └── delete └── permanentlyDeleteFolder.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /Admin/getSumOfDataInAccounts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | 4 | import json 5 | import requests 6 | import pprint # Allows Pretty Print of JSON 7 | import os # Allows for the clearing of the Terminal Window 8 | import csv # Allows outputting to CSV file 9 | import time, datetime 10 | 11 | """ 12 | A Script to iterate over all members of a team and extract the data they're using. 13 | 14 | An output file will be created using this naming convention 15 | 16 | YY-MM-DD-AccountsDataSummary.csv 17 | 18 | It will contain columns: 19 | email, account status, space used (bytes), space used, space allocated (bytes), space allocated 20 | 21 | 22 | NOTE: 23 | If Member Space Allocation is NOT USED, or if users are part of a exception list: 24 | - They users will show a Zero for columns 'space allocated (bytes)' and 'space allocated' 25 | - They are part of the Teams overall space allowance 26 | 27 | """ 28 | 29 | """ 30 | Set your OAuth Token here with 'Team Member Management' permissions 31 | """ 32 | aTokenTMM = '' # Team Member Management 33 | aTokenTMFA = '' # Team Member File Access 34 | 35 | 36 | """ 37 | DO NOT EDIT BELOW THIS POINT 38 | """ 39 | 40 | fileName = 'AccountsDataSummary.csv' 41 | 42 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 43 | 44 | userSharedLinks =[] # List of users and their shared links. 45 | failedToListLinks = [] # List of users we failed to list links for. 46 | 47 | 48 | ############################################# 49 | # Function to return a string representation of bytes 50 | ############################################# 51 | def getBytesAsGB_MB_KB( num ): 52 | 53 | # variables to convert form bytes to other format. 54 | terabyte = 1099511627776 55 | gigabyte = 1073741824 56 | megabyte = 1048576 57 | kilobyte = 1024 58 | 59 | kb = 0 60 | mb = 0 61 | gb = 0 62 | tb = 0 63 | 64 | if ( type(num) is str ): 65 | if ( '.' in num ): 66 | num = int(float(num)) 67 | else: 68 | num = int( num ) 69 | 70 | # Copy of the NUM we'll reduce as we progress 71 | numRemains = num 72 | 73 | # Check for GB 74 | if (numRemains > terabyte): 75 | tb = numRemains / terabyte 76 | numRemains = numRemains - ( tb * terabyte ) 77 | 78 | if (numRemains > gigabyte): 79 | gb = numRemains / gigabyte 80 | numRemains = numRemains - ( gb * gigabyte ) 81 | 82 | if (numRemains > megabyte ): 83 | mb = numRemains / megabyte 84 | numRemains = numRemains - ( mb * megabyte ) 85 | 86 | if (numRemains > kilobyte ): 87 | kb = numRemains / kilobyte 88 | numRemains = numRemains - ( kb * kilobyte ) 89 | else: 90 | kb = numRemains 91 | 92 | return ('%s TB, %s GB, %s MB, %s KB' % (tb, gb,mb,kb)) 93 | 94 | ############################################# 95 | # Function to return current Timestamp 96 | ############################################# 97 | def getTimeYMDHM(): 98 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M') 99 | return lRightNow; 100 | 101 | 102 | ############################################# 103 | # Function to print Message to console in a tidy box 104 | ############################################# 105 | def printmessageblock( str ): 106 | print "\n*********************************************************" 107 | print "* %s" % (str) 108 | print "*********************************************************\n" 109 | return; 110 | 111 | ############################################# 112 | # Function to return a Token which used a 113 | # Team Member File Access token & Member ID 114 | ############################################# 115 | def getTokenWithTeamMemberFileAccess( aTokenTMFA, member_id ): 116 | lHeadersTMFA = {'Content-Type': 'application/json', 117 | 'Authorization': 'Bearer %s' % aTokenTMFA, 118 | 'Dropbox-API-Select-User': '%s' % str(member_id)} 119 | return lHeadersTMFA; 120 | 121 | 122 | ############################################# 123 | # Function to get list of Team Members paths 124 | ############################################# 125 | def getTeamMembersSpaceUsage( aMember ): 126 | aURL = 'https://api.dropboxapi.com/2/users/get_space_usage' 127 | aData = json.dumps(None) 128 | 129 | timestart = datetime.datetime.fromtimestamp(time.time()) 130 | 131 | lHeaders = getTokenWithTeamMemberFileAccess( aTokenTMFA, aMember[1][0] ) 132 | 133 | 134 | print ("\n+ %s" % aMember[0]) 135 | aResult = requests.post(aURL, headers=lHeaders, data=aData) 136 | print ("+++") 137 | 138 | # If we don't get a 200 HTML response code, we didn't get a result. 139 | if( aResult.status_code != 200 ): 140 | printmessageblock ('* Failed to get a response to call for get_space_usage. \nWe got an error [%s] with text "%s"' % (aResult.status_code, aResult.text)) 141 | return []; 142 | 143 | # Note the JSON response 144 | spaceUsage = aResult.json() 145 | 146 | used = spaceUsage['used'] 147 | allocated = spaceUsage['allocation']['user_within_team_space_allocated'] 148 | 149 | timestop = datetime.datetime.fromtimestamp(time.time()) 150 | print ( '/get_space_usage Time taken: %s seconds' % (timestop-timestart).total_seconds()) 151 | 152 | return [used,allocated]; 153 | 154 | """ 155 | ############################################# 156 | # Step 0 157 | # Clear the terminal window, not essential but makes it easier to read this way. 158 | ############################################# 159 | """ 160 | 161 | os.system('cls' if os.name=='nt' else 'clear') 162 | 163 | 164 | 165 | """ 166 | ############################################# 167 | # Step 1 168 | # 1. Check if there 'aToken' variable is set 169 | # 2. If not, ask the user to enter it. 170 | ############################################# 171 | """ 172 | if (aTokenTMFA == ''): 173 | aTokenTMFA = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 174 | 175 | 176 | if (aTokenTMM == ''): 177 | aTokenTMM = raw_input('Enter your Dropbox Business API App token (Team Member Management permission): ') 178 | 179 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % aTokenTMM} 180 | 181 | 182 | 183 | """ 184 | ############################################# 185 | # Step 2 186 | # 1. Get list of all Dropbox Team Members 187 | # 2. Create in memory list of them. 188 | ############################################# 189 | """ 190 | hasMore = True; 191 | loopCounter = 0 192 | dbxUsers = [] 193 | 194 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 195 | aData = json.dumps({'limit': 300}) 196 | 197 | print ("> Retrieving Dropbox Users via API") 198 | timestart = datetime.datetime.fromtimestamp(time.time()) 199 | 200 | while hasMore: 201 | 202 | print (".") 203 | """ Make the API call """ 204 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 205 | 206 | print ("...") 207 | 208 | # If we don't get a 200 HTML response code, we didn't get a result. 209 | if( aResult.status_code != 200 ): 210 | print ('>>> Failed to get a response to call for /team/members/list') 211 | print (aResult.text) 212 | exit(); 213 | 214 | # Note the JSON response 215 | members = aResult.json() 216 | 217 | # Iterate over the Members in the JSON 218 | for aMember in members['members']: 219 | #pprint.pprint( aMember ) 220 | dbxUsers.append( [aMember['profile']['email'], [aMember['profile']['team_member_id'], aMember['profile']['status']['.tag']] ] ) 221 | 222 | hasMore = members['has_more'] # Note if there's another cursor call to make. 223 | 224 | # If it's the first run, from this point onwards the API call is the /continue version. 225 | if ( loopCounter >= 0 ): 226 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 227 | aData = json.dumps({'cursor': members['cursor']}) 228 | loopCounter += 1 229 | 230 | timestop = datetime.datetime.fromtimestamp(time.time()) 231 | print (" We have the Dropbox users in memory from %s API Calls. it took %s seconds.") % (loopCounter,(timestop-timestart).total_seconds()) 232 | 233 | 234 | 235 | """ 236 | ############################################# 237 | # Step 3 238 | # 1. Iterate over all Members of the Team, make a call to get their space usage, and write to file. 239 | ############################################# 240 | """ 241 | 242 | # Open a file to write to 243 | newFileName = ("%s-" + fileName) % getTimeYMDHM() 244 | 245 | with open( newFileName, 'wt') as csvfile: 246 | # Define the delimiter 247 | writer = csv.writer(csvfile, delimiter=',') 248 | # Write the Column Headers 249 | writer.writerow(['email','account status','space used (bytes)','space used', 'space allocated (bytes)', 'space allocated']) 250 | 251 | # Iterate over the members 252 | for aMember in dbxUsers: 253 | 254 | # if ( aMember[0] == 'jeremy@hanfordinc.com'): 255 | result = getTeamMembersSpaceUsage( aMember ) 256 | 257 | new_item = [aMember[0], aMember[1][1], result[0], getBytesAsGB_MB_KB(result[0]), result[1], getBytesAsGB_MB_KB(result[1])] 258 | #pprint.pprint(new_item) 259 | writer.writerow(new_item) 260 | 261 | 262 | 263 | 264 | 265 | 266 | """ 267 | ############################################# 268 | # Step 4 269 | # 1. Output how long the script took to run. 270 | ############################################# 271 | """ 272 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 273 | printmessageblock( " Script finished running, it took %s seconds." % ((totalTimeStop-totalTimeStart).total_seconds() ) ) 274 | -------------------------------------------------------------------------------- /Admin/splitTeam.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import pprint # Allows Pretty Print of JSON 4 | import os # Allows for the clearing of the Terminal Window 5 | import csv # Allows outputting to CSV file 6 | import time, datetime 7 | import sys 8 | 9 | 10 | """ 11 | ******************************************************************************************************************** 12 | The intention of this script is to: 13 | Iterate through a list of users ( users.csv ), remove that member from the source team (based on the API token provided gFromTeamTOKEN) 14 | returning their account to being a personal individual account. 15 | Then immediately inviting the user to join new team (based on the API token provided gToTeamTOKEN). 16 | 17 | NOTE: 18 | CSV file expects email, first name, last name 19 | One user per row 20 | 21 | Users in target Team are invited as standard team members. These users will need to ACCEPT the invitation before joining the team. 22 | Once they accept, they will enter the invite flow, and must select option to transfer their content to company. 23 | If you need to promote anyone to an Administrative level, do so using the Admin web pages. 24 | 25 | NOTES: 26 | When you remove a team member from a team: 27 | 1. Account will be disconnected from the team and converted to an individual account 28 | 2. Member will keep unshared files and folders, and shared folders that they own 29 | 3. Member won't have access to team-owned folders that they were invited to after joining the team 30 | 4. Member will still have access to Paper docs that they own and are private. They will lose access to paper documents that were 31 | shared with them, and also paper documents they owned and shared with others! 32 | 33 | By default the script runs in MOCK or test mode. Edit the variable 'gMockRun' to make it run for real. 34 | By default adding users to target team will send them an invite email, edit variable 'gSendWelcomeEmail' to stop this. 35 | Script logs most console content to a file 'logfile.txt' in the location script is executed. 36 | 37 | ** WARNING ** 38 | If you enter incorrect Target Team Token, users accounts could be orphaned as they'll be removed from Source Team but not added to 39 | the Target Team. 40 | 41 | 42 | Requirements: 43 | Script tested on Python 3.6.5 44 | 45 | One Dropbox API Token is needed from each team, source team and target team. Inserted just below this comments section. 46 | Permissions needed on token: 47 | 48 | Source Team: 49 | - team_data.member "View structure of your team's and members' folders" 50 | - members.delete "Remove and recover your team members' accounts" 51 | 52 | Target Team: 53 | - team_data.member "View structure of your team's and members' folders" 54 | - members.write "View and manage your team membership" 55 | 56 | 57 | Pre-requisites: 58 | * Scripts requires library 'Requests' - You can install using "pip install requests" 59 | 60 | 61 | 62 | ******************************************************************************************************************** 63 | """ 64 | gFromTeamTOKEN = '' # Scoped API token for SOURCE team to remove user from 65 | gToTeamTOKEN = '' # Scoped API token for TARGET team to add user to 66 | 67 | 68 | 69 | gMockRun = True # If True the script emulates the actual call to the API so no account moves done 70 | gSendWelcomeEmail = False # If True the script will send a Welcome / invite to user added to target team. 71 | gRetainTeamShares = False # If True, when users removed from source team they will retain access to Dropbox Folders ( not paper folders ) already 72 | # explicitly shared with them (not via group membership) 73 | 74 | 75 | 76 | """ 77 | ******************************************************************************************************************** 78 | DO NOT EDIT BELOW THIS POINT 79 | ******************************************************************************************************************** 80 | """ 81 | 82 | 83 | # Track how long script takes to run 84 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 85 | 86 | # Global Variables 87 | gUsers = [] 88 | 89 | 90 | ############################################# 91 | # Function to return a string representation of time taken 92 | ############################################# 93 | def getTimeInHoursMinutesSeconds( sec ): 94 | sec = int(sec) 95 | hrs = sec / 3600 96 | sec -= 3600*hrs 97 | 98 | mins = sec / 60 99 | sec -= 60*mins 100 | 101 | return '%s hrs, %s mins, %s sec' % ( hrs, mins, sec); 102 | 103 | ############################################# 104 | # Function to print Message to console in a tidy box 105 | ############################################# 106 | def printmessageblock( str ): 107 | print ("\n*********************************************************") 108 | print ("* %s" % (str)) 109 | print ("*********************************************************\n") 110 | return; 111 | 112 | ############################################# 113 | # Step 0 114 | # Clear the terminal window, not essential but makes it easier to read this way. 115 | ############################################# 116 | 117 | os.system('cls' if os.name=='nt' else 'clear') 118 | 119 | 120 | ############################################# 121 | # Step 1 122 | # Check that there's a From and To Token provided. 123 | ############################################# 124 | if (len( gFromTeamTOKEN ) <= 0 or len( gToTeamTOKEN ) <=0 ): 125 | printmessageblock ( "It would appear you're missing one of the necessary API Tokens. Ending script." ) 126 | exit() 127 | 128 | 129 | ############################################# 130 | # Step 1 131 | # Check if user wants to proceed. This could be distructive in removing users from Team. 132 | ############################################# 133 | 134 | printmessageblock('Are you sure you wish to proceed with running this script? ') 135 | if (not gMockRun): 136 | print( "If you proceed, team members listed in CSV file and found in the corresponding source API team will be removed from that team.") 137 | print( "Script will attempt then to add them to target team") 138 | else: 139 | print( "You are in MOCK RUN mode so no accounts will be removed or added.") 140 | 141 | lsAnswer = input("\nType 'y' to proceed or 'n' to cancel this script: ") 142 | 143 | if ( lsAnswer == 'y' or lsAnswer == 'Y'): 144 | print( "\nExecuting script" ) 145 | elif ( lsAnswer == 'n' or lsAnswer == 'N'): 146 | print( '\nExiting script\n') 147 | exit() 148 | else: 149 | print("\n\nUser did not enter a 'n' or a 'y' input. Ending script.") 150 | exit(); 151 | 152 | ############################################# 153 | # Step 2 154 | # Note the standard output to console 155 | # Redirect standard output to File until end of script. 156 | ############################################# 157 | 158 | print ( "Starting script, further outputs to log file." ) 159 | # Note the standard output 160 | gstdout = sys.stdout 161 | # Redirect standard output to log file 162 | sys.stdout = open('logfile.txt', 'w') 163 | 164 | 165 | ############################################# 166 | # Step 3 167 | # Get the list of users to remove from source team 168 | # and add to target team 169 | ############################################# 170 | # Open a file to read from 171 | with open( 'users.csv', 'r') as csvfileRead: 172 | # Open file to read from 173 | reader = csv.reader(csvfileRead) 174 | 175 | gUsers = list(reader) 176 | 177 | 178 | ############################################# 179 | # Step 4 180 | # Iterate through each user, 181 | # uninvite from Source Team 182 | ############################################# 183 | 184 | # Details for source team 185 | aHeadersSource = {'Content-Type': 'application/json', 186 | 'Authorization': 'Bearer %s' % gFromTeamTOKEN} 187 | aURLSource = 'https://api.dropboxapi.com/2/team/members/remove' 188 | 189 | # Details for target team 190 | aHeadersTarget = {'Content-Type': 'application/json', 191 | 'Authorization': 'Bearer %s' % gToTeamTOKEN} 192 | aURLTarget = 'https://api.dropboxapi.com/2/team/members/add_v2' 193 | 194 | 195 | for user in gUsers: 196 | 197 | aEmailAddress = user[0] 198 | aFirstName = user[1] 199 | aSurname = user[2] 200 | 201 | # Set Users Email into parameters 202 | aData = json.dumps({'user': {'.tag': 'email', 'email': aEmailAddress}, 'wipe_data': False, 'keep_account': True, 'retain_team_shares': gRetainTeamShares}) 203 | 204 | print( "\n------------------------------------------------------------------------" ) 205 | print( "Attempting to remove %s from source team." % aEmailAddress) 206 | 207 | """ Make the API call """ 208 | if ( not gMockRun ): 209 | aResult = requests.post(aURLSource, headers=aHeadersSource, data=aData) 210 | 211 | #If we don't get a 200 HTML response code, we didn't get a result. 212 | if( aResult.status_code != 200 ): 213 | print ('-- Failed to remove %s from team. %s' % (aEmailAddress, aResult.text)) 214 | continue; 215 | else: 216 | print ('++ Successfully removed %s from team. ' % (aEmailAddress)) 217 | else: 218 | print ('++ MOCK RUN: Successfully removed %s from team.' % (aEmailAddress)) 219 | 220 | 221 | ########################################## 222 | # Now try invite them to the target team! 223 | ########################################## 224 | 225 | # Set Users Email into parameters 226 | aData = json.dumps({"new_members": [{ 227 | "member_email": aEmailAddress, 228 | "member_given_name": aFirstName, 229 | "member_surname": aSurname, 230 | "send_welcome_email": gSendWelcomeEmail 231 | }], 232 | "force_async": False 233 | }) 234 | 235 | print( "\nAttempting to add %s to target team." % aEmailAddress) 236 | 237 | if ( not gMockRun ): 238 | """ Make the API call """ 239 | aResult = requests.post(aURLTarget, headers=aHeadersTarget, data=aData) 240 | 241 | # If we don't get a 200 HTML response code, we didn't get a result. 242 | if( aResult.status_code != 200 ): 243 | print ('-- Failed to add %s to target team. %s' % (aEmailAddress, aResult.text)) 244 | continue; 245 | else: 246 | print ('++ Successfully added %s to target team.' % (aEmailAddress)) 247 | else: 248 | print ('++ MOCK RUN: Successfully added %s to target team.' % (aEmailAddress)) 249 | 250 | 251 | 252 | ############################################# 253 | # Final step 254 | # 1. Output how long the script took to run. 255 | ############################################# 256 | 257 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 258 | totalTimeInSeconds = (totalTimeStop-totalTimeStart).total_seconds() 259 | timeAsStr = getTimeInHoursMinutesSeconds( totalTimeInSeconds ) 260 | printmessageblock( " Script finished running, it took %s." % ( timeAsStr ) ) 261 | 262 | # Put standard output back to console 263 | sys.stdout = gstdout 264 | print( "Script finished") 265 | -------------------------------------------------------------------------------- /Admin/wipe_external_ids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | 4 | import json 5 | import requests 6 | import os # Allows for the clearing of the Terminal Window 7 | import csv # Allows outputting to CSV file 8 | import time, datetime 9 | import sys 10 | import pprint 11 | 12 | """ 13 | The intention of this script is to: 14 | 15 | - Load a CSV list (config.csv) of users to wipe External IDs for 16 | - Iterate over all members of a team 17 | - If team member email is in the CSV list, we'll attempt to set its External ID to "" 18 | 19 | Requirements: 20 | Script writen and tested on Python 3.6.5 21 | 22 | Dropbox API Token needs to be inserted just below this comments section. 23 | It needs to have the following scoped permissions: 24 | 25 | - team_data.member 26 | - members.write 27 | 28 | 29 | Pre-requisites: 30 | * Scripts requires library 'Requests' - You can install using "pip install requests" 31 | 32 | """ 33 | 34 | """ 35 | Set your OAuth Tokens here 36 | """ 37 | gTokenTMM = '' # Insert SCOPED API token here 38 | 39 | # Source File Here 40 | gListOfMembersToWorkOn = 'config.csv' 41 | 42 | # Flag to control actually permanently wipe of 'external_id' 43 | gRunInTestMode = False 44 | 45 | 46 | 47 | 48 | ############################################# 49 | # Function to return current Timestamp 50 | ############################################# 51 | def getTimeYMDHM(): 52 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M') 53 | return lRightNow; 54 | 55 | def getPrettyTime(): 56 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%H:%M:%S %d-%m-%Y') 57 | return lRightNow; 58 | 59 | ############################################# 60 | # Function to print Message to console in a tidy box 61 | ############################################# 62 | def printmessageblock( str ): 63 | print ("\n*********************************************************") 64 | print ("* %s" % (str)) 65 | print ("*********************************************************\n") 66 | return; 67 | 68 | 69 | ############################################# 70 | # Function to print Message to console in a tidy box 71 | ############################################# 72 | def getTimeInHoursMinutesSeconds( sec ): 73 | return time.strftime("%H hrs %M mins %S sec", time.gmtime(sec)) 74 | 75 | ############################################# 76 | # Function to wipe members 'external_id' 77 | ############################################# 78 | def wipeExternalID(email, team_member_id): 79 | 80 | aURL = "https://api.dropboxapi.com/2/team/members/set_profile" 81 | aData = json.dumps({"user": { ".tag": "team_member_id", "team_member_id": team_member_id}, "new_external_id": ""}) 82 | lHeadersTMFA = {'Content-Type': 'application/json', 83 | 'Authorization': 'Bearer %s' % gTokenTMM} 84 | 85 | aResult = None 86 | 87 | if (not gRunInTestMode): 88 | aResult = requests.post(aURL, headers=lHeadersTMFA, data=aData) # Wipe 'external_id' 89 | print ( "Wiped External ID for %s | %s" % (email,team_member_id) ) 90 | else: 91 | print ( "Testing call to wipe External ID for %s | %s" % (email,team_member_id) ) 92 | return True 93 | 94 | if( aResult.status_code != 200 ): 95 | print ( "ERROR: Failed to wipe External ID for email: %s, error code %s, '%s'" % (email, aResult.status_code, aResult.text)) 96 | return False 97 | 98 | return True 99 | 100 | 101 | """ 102 | # ############################################ 103 | # Step 0 104 | # Clear the terminal window, not essential but makes it easier to read this way. 105 | # ############################################ 106 | """ 107 | 108 | os.system('cls' if os.name=='nt' else 'clear') 109 | 110 | print ( "Starting: %s" % getPrettyTime() ) 111 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 112 | 113 | 114 | """ 115 | # ############################################ 116 | # Step 1 117 | # Get a list of users to wipe 'external_id' on. 118 | # If empty or not found script will stop running. 119 | # ############################################ 120 | """ 121 | 122 | gUsersToChange = {} 123 | bAnalyzeAll = False 124 | 125 | # Check we have a config file 126 | bHaveCSV = os.path.isfile( gListOfMembersToWorkOn ) 127 | 128 | if (not bHaveCSV): 129 | printmessageblock('We could not find config file listing users to work on. Ending script! ') 130 | print ( "Stopping: %s" % getPrettyTime() ) 131 | exit(); 132 | 133 | # Open file of users to analyze 134 | with open( gListOfMembersToWorkOn, 'r') as csvfileRead: 135 | # Open file to read from 136 | reader = csv.reader(csvfileRead) 137 | 138 | #Iterate through each row of the CSV. 139 | for row in reader: 140 | gUsersToChange[row[0].lower()] = row[0].lower() # Lower case so we can compare to Dropbox ( always lowercase ) 141 | 142 | if ( len(gUsersToChange) <= 0 ): 143 | 144 | # Check that we have users 145 | printmessageblock("We could not find any users in config file '%s' to work on. Ending script." % aListOfMembersToReportOn) 146 | print ( "Stopping: %s" % getPrettyTime() ) 147 | exit(); 148 | 149 | 150 | """ 151 | # ############################################ 152 | # Step 1 153 | # Get a list of all team members to locate the ones we've to change 154 | # ############################################ 155 | """ 156 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % gTokenTMM} 157 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 158 | aData = json.dumps({'limit': 100, 'include_removed': True}) 159 | 160 | hasMore = True; 161 | loopCounter = 0; 162 | totalMembers = 0 163 | members_wiped = 0 164 | 165 | while hasMore: 166 | """ Make the API call """ 167 | print (">>> API call") 168 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 169 | print ("<<< Results") 170 | 171 | # If we don't get a 200 HTML response code, we didn't get a result. 172 | if( aResult.status_code != 200 ): 173 | printmessageblock ('* Failed to get a response to call for /team/members/list') 174 | print (aResult.text) 175 | exit(); 176 | 177 | # Note the JSON response 178 | members = aResult.json() 179 | totalMembers += len(members['members']) # Keep a count of total members ( this will be verfied and unverfied accounts ) 180 | 181 | # Iterate over the Members in the JSON 182 | for aMember in members['members']: 183 | 184 | memberEmail = aMember['profile']['email'].strip() 185 | team_member_id = aMember['profile']['team_member_id'].strip() 186 | 187 | # If this member is in our CSV list of members to change 188 | if ( memberEmail in gUsersToChange ): 189 | wipeExternalID(memberEmail, team_member_id) 190 | members_wiped = members_wiped + 1 191 | 192 | 193 | hasMore = members['has_more'] # Note if there's another cursor call to make. 194 | 195 | # If it's the first run, from this point onwards the API call is the /continue version. 196 | if ( loopCounter == 0 ): 197 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 198 | aData = json.dumps({'cursor': members['cursor']}) 199 | 200 | 201 | 202 | """ 203 | ############################################# 204 | # Step 7 205 | # 1. Output how long the script took to run. 206 | ############################################# 207 | """ 208 | 209 | print ( "\n\nIterated over members: " + str(totalMembers)) 210 | print ( "Wiped external ID for: " + str(members_wiped) + "\n\n") 211 | 212 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 213 | totalTimeInSeconds = (totalTimeStop-totalTimeStart).total_seconds() 214 | timeAsStr = getTimeInHoursMinutesSeconds( totalTimeInSeconds ) 215 | printmessageblock( " Script finished running, it took %s seconds." % ( timeAsStr ) ) 216 | 217 | print ( "\nStopping: %s" % getPrettyTime() ) 218 | 219 | 220 | -------------------------------------------------------------------------------- /AuditLog/GetEvents.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$start, # mm/dd/yyyy hh:mm:ss 3 | [string]$end, # mm/dd/yyyy hh:mm:ss 4 | [string]$category # apps, devices, groups, logins, members, passwords, sharing, team_admin_actions 5 | ) 6 | 7 | $epochDate = "1/1/1970 00:00:00" 8 | $object = New-Object psobject 9 | 10 | if($start) { 11 | $start = (New-TimeSpan -Start $epochDate -End (Get-Date -Date $start)).TotalSeconds 12 | $object | Add-Member -MemberType NoteProperty -Name start_ts -Value ([int]$start * 1000) 13 | } 14 | 15 | if($end) { 16 | $end = (New-TimeSpan -Start $epochDate -End (Get-Date -Date $end)).TotalSeconds 17 | $object | Add-Member -MemberType NoteProperty -Name end_ts -Value ([int]$end * 1000) 18 | } 19 | 20 | if($category) { 21 | $object | Add-Member -MemberType NoteProperty -Name category -Value $category 22 | } 23 | 24 | #Write-Host (ConvertTo-Json $object) 25 | 26 | # Prompt for Team Auditing permission 27 | $token = Read-Host -Prompt "Enter your Dropbox Business API App token (Team Auditing permission): " 28 | $token = "Bearer $token" 29 | 30 | Write-Host 31 | Write-Host "Audit Log:" -ForegroundColor Green 32 | Write-Host 33 | 34 | $has_more = $true 35 | $cursor = $null 36 | 37 | # Continue to call get_events as long as there are more events 38 | while($has_more) { 39 | 40 | if($cursor) { 41 | if($object.cursor) { 42 | $object.cursor = $cursor 43 | } else { 44 | $object | Add-Member -MemberType NoteProperty -Name cursor -Value $cursor 45 | } 46 | } 47 | 48 | # Make API Call for events 49 | $report = Invoke-RestMethod -Uri https://api.dropbox.com/1/team/log/get_events -Body (ConvertTo-Json $object) -ContentType application/json -Headers @{Authorization = $token } -Method Post 50 | $report.events 51 | 52 | $has_more = [System.convert]::ToBoolean($report.has_more) 53 | $cursor = $report.cursor 54 | 55 | } 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /AuditLog/GetMemberJoins.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import argparse 4 | import csv 5 | import sys 6 | 7 | reload(sys) 8 | sys.setdefaultencoding('UTF8') 9 | 10 | parser = argparse.ArgumentParser(description='Gets Member Join Dates from the Audit Log') 11 | args = parser.parse_args() 12 | 13 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Auditing permission): ') 14 | 15 | # Get audit log events 16 | def getEvents(event_category, cursor): 17 | data = {"limit":1000, "category":event_category} 18 | if cursor is not None: 19 | data["cursor"] = cursor 20 | 21 | request = urllib2.Request('https://api.dropbox.com/1/team/log/get_events', json.dumps(data)) 22 | request.add_header("Authorization", "Bearer "+dfbToken) 23 | request.add_header("Content-type", 'application/json') 24 | try: 25 | response = json.loads(urllib2.urlopen(request).read()) 26 | events = response["events"] 27 | if response["has_more"]: 28 | events = events + getEvents(event_category, cursor=response["cursor"]) 29 | return events 30 | 31 | # Exit on error here. Probably bad OAuth token. Show DfB response. 32 | except urllib2.HTTPError, error: 33 | parser.error(error.read()) 34 | 35 | 36 | # Print member_join events 37 | csvwriter = csv.writer(sys.stdout) 38 | csvwriter.writerow(['Email', 'Join Date']) 39 | for event in getEvents("members", None): 40 | if (event["event_type"] == 'member_join'): 41 | csvwriter.writerow([event["email"], event["time"]]) 42 | -------------------------------------------------------------------------------- /CSV_PDF_Migration_Script.py: -------------------------------------------------------------------------------- 1 | ## Script for scanning a CSV file for links to documents, download those documents, 2 | ## and scan the subsequent files (assuming PDF) for additional content to download 3 | 4 | ## THIS VERSION ASSUMES FILES WILL BE PLACED IN THE DROPBOX FOLDER USING THE DESKTOP APP. MODIFICATIONS ARE NEEDED FOR USE WITH DROPBOX API 5 | 6 | import csv 7 | import requests 8 | import re 9 | import PyPDF2 10 | from os.path import basename 11 | import os 12 | from urllib.parse import urlparse 13 | import pikepdf 14 | from urllib.request import Request, urlopen 15 | 16 | ## Open CSV file (enter path of CSV file below) 17 | f = open('file.csv') 18 | csv_f = csv.reader(f) 19 | for row in csv_f: 20 | 21 | url = row[3] 22 | projectid = row[0] 23 | 24 | ## Create project folder in Dropbox if not exist 25 | if not os.path.exists('PATH_TO_DROPBOX_FOLDER'+projectid): 26 | os.makedirs('PATH_TO_DROPBOX_FOLDER'+projectid) 27 | 28 | ## Download file and add to project folder 29 | myfile = requests.get(url, allow_redirects=True) 30 | hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11', 31 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 32 | 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 33 | 'Accept-Encoding': 'none', 34 | 'Accept-Language': 'en-US,en;q=0.8', 35 | 'Connection': 'keep-alive'} 36 | req = Request(url, headers=hdr) 37 | response = urlopen(req) 38 | fname = basename(response.url) 39 | 40 | print('Downloading '+str(fname)+" for project "+projectid) 41 | 42 | ## Here is where the file is saved to Dropbox. Dropbox API can be used here to upload directly 43 | open('PATH_TO_DROPBOX_FOLDER'+projectid+'/'+str(fname), 'wb').write(myfile.content) 44 | 45 | ## Download files in PDF 46 | file = "PATH_TO_DROPBOX_FOLDER"+projectid+"/"+str(fname) 47 | pdf_file = pikepdf.Pdf.open(file) 48 | urls = [] 49 | # iterate over PDF pages 50 | for page in pdf_file.pages: 51 | for annots in page.get("/Annots") or []: 52 | if annots is not None: 53 | uri = annots.get("/A").get("/URI") 54 | ## Find URLs in PDFs and download linked files 55 | if uri is not None: 56 | print("[+] URL Found:", uri) 57 | uriString = str(uri) 58 | innerfile = requests.get(uriString, allow_redirects=True) 59 | reqInner = Request(uriString, headers=hdr) 60 | responseInner = urlopen(reqInner) 61 | fnameInner = basename(responseInner.url) 62 | open('PATH_TO_DROPBOX_FOLDER'+projectid+'/'+str(fnameInner), 'wb').write(innerfile.content) 63 | 64 | ### Done! -------------------------------------------------------------------------------- /Files/FileSizeQuotaAlert.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import print_function 3 | from __future__ import division 4 | import json 5 | import argparse 6 | import csv 7 | from urllib2 import Request, urlopen, HTTPError 8 | from multiprocessing.dummy import Pool as ThreadPool 9 | import sys 10 | reload(sys) 11 | sys.setdefaultencoding('UTF8') # @UndefinedVariable 12 | 13 | ''' 14 | FileSizeQuotaAlert.py 15 | Scan all members of a Dropbox team and report files that are at or over a given 16 | quota value 17 | REQUIRES: Team member file access permission 18 | ''' 19 | UNITS = {"MB": 1024 ** 2, "GB": 1024 ** 3, "TB": 1024 ** 4} 20 | 21 | dbxApiV2 = None 22 | fileQuota = None 23 | 24 | parser = argparse.ArgumentParser( 25 | description='Checks team member disk usage and reports on files that exceed a given quota') 26 | parser.add_argument('-o', '--output', nargs='?', default=sys.stdout, 27 | type=argparse.FileType('wb'), 28 | help='path to output file for CSV generation, default: stdout') 29 | parser.add_argument('-q', '--quota', nargs='?', default=1, type=int, 30 | help='file quota to check usage against, in units given with -u, default:1TB') 31 | parser.add_argument('-u', '--units', nargs='?', choices=['MB', 'GB', 'TB'], 32 | default='GB', help='unit value for quota, must be one of MB, GB, TB, default: GB') 33 | parser.add_argument("-l", "--limit", nargs='?', default=1000, type=int, 34 | help='limit max records returned per Dropbox API call, default: 1000') 35 | parser.add_argument("-t", "--threads", nargs='?', default=4, type=int, 36 | help="worker thread count, default: 4") 37 | parser.add_argument("-v", "--verbose", action="store_const", 38 | default=False, const=True, help='enable verbose output') 39 | args = parser.parse_args() 40 | 41 | def main(): 42 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 43 | 44 | if args.verbose: 45 | dumpArguments() 46 | 47 | global fileQuota 48 | fileQuota = args.quota * UNITS[args.units] 49 | 50 | log("Creating Dropbox V2 API Client") 51 | global dbxApiV2 52 | dbxApiV2 = DbxApi(DbxApi.DBX_API_V2, dfbToken) 53 | 54 | log("Collecting Member List...") 55 | members = getDfbMembers(None) 56 | # Filter out invited members as they can't consume any quota yet 57 | activeMembers = [member for member in members if member.status != "invited"] 58 | log("Got {} total team members ({} active, {} suspended, {} invited)" 59 | .format( 60 | len(members), len(activeMembers), 61 | len(getMemberSublist(members, "suspended")), 62 | len(getMemberSublist(members, "invited")) 63 | )) 64 | 65 | log("Collecting file quota information - this may take a while...") 66 | pool = ThreadPool(args.threads) 67 | members = pool.map(getFileQuotaUsage, activeMembers) 68 | pool.close() 69 | pool.join() 70 | 71 | # Write final output 72 | log("Processing complete, writing output to {}".format(args.output.name)) 73 | dumpCsvFile(members) 74 | 75 | def getDfbMembers(cursor): 76 | """Get a list of all Dropbox for Business team members""" 77 | if cursor is not None: 78 | data = {"cursor": cursor} 79 | endpoint = "/team/members/list/continue" 80 | else: 81 | data = {"limit": args.limit} 82 | endpoint = "/team/members/list" 83 | 84 | try: 85 | result = dbxApiV2.call(endpoint, None, json.dumps(data)) 86 | members = listToMemberObj(result["members"]) 87 | 88 | # Check to see if we got all team members, if not, get the rest 89 | if result["has_more"]: 90 | members = members + getDfbMembers(result["cursor"]) 91 | 92 | return members 93 | except HTTPError as error: 94 | parser.error(error.read()) 95 | 96 | def getFileQuotaUsage(member): 97 | """Get file size information for a Dropbox for Business team member""" 98 | try: 99 | data = {"path": "", "recursive": True} 100 | response = dbxApiV2.call("/files/list_folder", member.id, json.dumps(data)) 101 | fileData = response["entries"] 102 | member.files.extend(getFileQuotaViolations(fileData)) 103 | hasMore = response["has_more"] 104 | while hasMore: 105 | data = {"cursor": response["cursor"]} 106 | response = dbxApiV2.call("/files/list_folder/continue", member.id, json.dumps(data)) 107 | fileData = response["entries"] 108 | member.files.extend(getFileQuotaViolations(fileData)) 109 | hasMore = response["has_more"] 110 | return member 111 | except HTTPError as httpError: 112 | # catch server errors and retry that user 113 | if 500 <= httpError.code <= 599: # Server error codes 500-599 114 | log("Encountered server error ({}) for user {}, retrying..." 115 | .format(httpError,member.fullName), True) 116 | # clear existing file quota info for user, and retry file quota request 117 | del member.files[:] 118 | return getFileQuotaUsage(member) 119 | elif 400 <= httpError.code <= 499: # Client error codes 400-499 120 | errStr = httpError.read() 121 | log("Client error for user {} (Status: {}): Error {}:{}" 122 | .format(member.fullName, member.status, httpError.code, errStr), True) 123 | return None 124 | 125 | def getFileQuotaViolations(files): 126 | violations = [] 127 | for f in files: 128 | if f[".tag"] != "file": # skip folders 129 | continue 130 | if f["size"] >= fileQuota: 131 | violations.append(File(f)) 132 | return violations 133 | 134 | def dumpCsvFile(members): 135 | """Write member information to a CSV file""" 136 | if args.output == sys.stdout: 137 | log("-------------------------- BEGIN CSV OUTPUT --------------------------") 138 | 139 | csvwriter = csv.writer(args.output) 140 | csvwriter.writerow(['First Name', 141 | 'Last Name', 142 | 'Email', 143 | 'File Name', 144 | 'File Path', 145 | 'File Size (bytes)', 146 | ]) 147 | for member in members: 148 | if member is not None and len(member.files) > 0: 149 | for f in member.files: 150 | csvwriter.writerow([member.firstName, 151 | member.lastName, 152 | member.email, 153 | f.name, 154 | f.path, 155 | str(f.size) 156 | ]) 157 | 158 | def listToMemberObj(memberList): 159 | """Convert a list of member info dicts into a list of Member Class objects""" 160 | members = [] 161 | for member in memberList: 162 | members.append(Member(member)) 163 | return members 164 | 165 | def getMemberSublist(members, status): 166 | sublist = [] 167 | for member in members: 168 | if member.status == status: 169 | sublist.append(member) 170 | return sublist 171 | 172 | def log(msg, isError=False): 173 | """Log information to stdout, or stderr based upon global verbosity setting""" 174 | if isError: 175 | print(msg, file=sys.stderr) 176 | return 177 | if args.verbose: 178 | print(msg) 179 | 180 | def dumpArguments(): 181 | log("Verbose output enabled") 182 | log("Output file set to {}".format(args.output.name)) 183 | log("Quota set to {}{}".format(args.quota, args.units)) 184 | log("Max records set to {}".format(args.limit)) 185 | log("Worker threads set to {}".format(args.threads)) 186 | 187 | class DbxApi: 188 | """DbxApi - Convenience wrapper class around Dropbox API calls""" 189 | DBX_API_V1 = "https://api.dropbox.com/1" 190 | DBX_API_V2 = "https://api.dropboxapi.com/2" 191 | 192 | def __init__(self, baseUrl, accessToken): 193 | self.baseUrl = baseUrl 194 | self.accessToken = accessToken 195 | 196 | def call(self, endpoint, mbrId=None, payload=None, setContent=True): 197 | if payload is not None: 198 | payload = payload.encode('utf8') 199 | request = Request(self.baseUrl + endpoint, payload) 200 | request.add_header("Content-type", 'application/json') 201 | else: 202 | request = Request(self.baseUrl + endpoint) 203 | request.add_header("Authorization", "Bearer " + self.accessToken) 204 | request.get_method = lambda: 'POST' 205 | 206 | if mbrId is not None: 207 | if self.baseUrl == self.DBX_API_V2: 208 | request.add_header("Dropbox-API-Select-User", mbrId) 209 | else: 210 | request.add_header("X-Dropbox-Perform-As-Team-Member", mbrId) 211 | 212 | try: 213 | return json.loads(urlopen(request).read()) 214 | except HTTPError: 215 | # raise exception to caller. 216 | raise 217 | 218 | class Member: 219 | """Member - Convenience wrapper class around a Dropbox for Business team member""" 220 | def __init__(self, member): 221 | self.firstName = member["profile"]["name"]["given_name"] 222 | self.lastName = member["profile"]["name"]["surname"] 223 | self.fullName = self.firstName + " " + self.lastName 224 | self.id = member["profile"]["team_member_id"] 225 | self.email = member["profile"]["email"] 226 | self.status = member["profile"]["status"][".tag"] 227 | self.quotaStatus = Quota.NORMAL 228 | # Quota values won't be present until getQuotaUsage() is called! 229 | self.quotaUsed = None 230 | self.quotaAllocated = None 231 | self.quotaType = None 232 | self.teamQuotaUsed = None 233 | self.files = [] 234 | 235 | class File: 236 | """Member - Convenience wrapper class around file metadata""" 237 | def __init__(self, f): 238 | self.id = f["id"] 239 | self.name = f["name"] 240 | self.path = f["path_display"] 241 | self.size = f["size"] 242 | self.rev = f["rev"] 243 | 244 | 245 | class Quota: 246 | """Enum for Quota status constants""" 247 | NORMAL = "NORMAL" 248 | WARN = "WARN" 249 | VIOLATION = "VIOLATION" 250 | 251 | if __name__ == '__main__': 252 | main() 253 | -------------------------------------------------------------------------------- /Files/ListTeamFiles.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import json 4 | import argparse 5 | import sys 6 | import csv 7 | from collections import Counter 8 | 9 | reload(sys) 10 | sys.setdefaultencoding('UTF8') 11 | 12 | parser = argparse.ArgumentParser(description='Lists all files by user in the DfB team.') 13 | parser.add_argument('-u', '--user', dest='users', action='append', help='Target user (email address) to scan. All team members will be returned if unspecified. You may pass multiple -u arguments.') 14 | 15 | args = parser.parse_args() 16 | 17 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 18 | 19 | #Look up a DfB member from an email address 20 | def getDfbMember(email): 21 | request = urllib2.Request('https://api.dropbox.com/1/team/members/get_info', json.dumps({'email':email})) 22 | request.add_header("Authorization", "Bearer "+dfbToken) 23 | request.add_header("Content-type", 'application/json') 24 | try: 25 | return json.loads(urllib2.urlopen(request).read()) 26 | 27 | # Exit on error here. Probably user not found or bad OAuth token. Show DfB response. 28 | except urllib2.HTTPError, error: 29 | parser.error(error.read()); 30 | 31 | 32 | # Get all DfB members, paging through results if necessary 33 | def getDfbMembers(cursor): 34 | data = {"limit":100} 35 | if cursor is not None: 36 | data["cursor"] = cursor 37 | 38 | request = urllib2.Request('https://api.dropbox.com/1/team/members/list', json.dumps(data)) 39 | request.add_header("Authorization", "Bearer "+dfbToken) 40 | request.add_header("Content-type", 'application/json') 41 | try: 42 | response = json.loads(urllib2.urlopen(request).read()) 43 | members = response["members"] 44 | 45 | if response["has_more"]: 46 | members = members + getDfbMembers(cursor=response["cursor"]) 47 | 48 | return members 49 | 50 | # Exit on error here. Probably bad OAuth token. Show DfB response. 51 | except urllib2.HTTPError, error: 52 | parser.error(error.read()) 53 | 54 | # List a member's dropbox 55 | def listFiles(memberEmail, memberId, csvwriter): 56 | cursor = None 57 | num_files = 0 58 | 59 | try: 60 | while True: 61 | params = {} 62 | if cursor is not None: 63 | params['cursor'] = cursor 64 | request = urllib2.Request('https://api.dropboxapi.com/1/delta', data=urllib.urlencode(params)) 65 | request.add_header("Authorization", "Bearer "+dfbToken) 66 | request.add_header("X-Dropbox-Perform-As-Team-Member", memberId) 67 | 68 | response_string = urllib2.urlopen(request).read() 69 | response = json.loads(response_string) 70 | 71 | for path, md in response["entries"]: 72 | if md is None: 73 | pass # Delete entry. Skip it. 74 | else: 75 | 76 | shared = False 77 | if 'parent_shared_folder_id' in md or 'shared_folder' in md: 78 | shared = True 79 | 80 | if md["is_dir"]: 81 | csvwriter.writerow([memberEmail, md["path"].encode("utf-8"), "Folder", "-", "-", md["modified"], str(shared)]) 82 | else: 83 | csvwriter.writerow([memberEmail, md["path"].encode("utf-8"), md["mime_type"], str(md["bytes"]), md["size"], md["modified"], str(shared)]) 84 | num_files += 1 85 | 86 | if response["reset"] and cursor is not None: 87 | sys.stderr.write(" ERROR: got a reset!") 88 | csvwriter.writerow([memberEmail, "/delta with cursor={!r} returned RESET".format(cursor), "ERROR", "-", "-", "-", "-"]) 89 | break 90 | 91 | cursor = response['cursor'] 92 | 93 | if not response['has_more']: 94 | break 95 | 96 | sys.stderr.write(" listed {} files/folders for {} \n".format(num_files, memberEmail)) 97 | 98 | 99 | except urllib2.HTTPError as error: 100 | csvwriter.writerow([memberEmail, "/delta with cursor={!r} unknown error".format(cursor), "ERROR", "-", "-", "-", "-", "-"]) 101 | sys.stderr.write(" ERROR: {}\n".format(error)) 102 | 103 | 104 | def formatSize(num, suffix='B'): 105 | for unit in ['','K','M','G','T','P','E','Z']: 106 | if abs(num) < 1000.0: 107 | return "%3.1f%s%s" % (num, unit, suffix) 108 | num /= 1024.0 109 | return "%.1f%s%s" % (num, 'Yi', suffix) 110 | 111 | 112 | members = [] 113 | if (args.users is not None): 114 | members = map(getDfbMember, args.users) 115 | else: 116 | members = getDfbMembers(None) 117 | 118 | csvwriter = csv.writer(sys.stdout) 119 | 120 | csvwriter.writerow(['User', 'Path', 'Type', 'Size (bytes)', 'Size (formatted)', 'Modified', 'Shared']) 121 | 122 | #TODO: Thread this? 123 | for member in members: 124 | if member["profile"]["status"] == "active": 125 | files = listFiles(member["profile"]["email"], member["profile"]["member_id"], csvwriter) 126 | -------------------------------------------------------------------------------- /Files/MoveData.py: -------------------------------------------------------------------------------- 1 | def move_data(token, member_id, from_path, to_path): 2 | url = 'https://api.dropboxapi.com/1/fileops/move' 3 | headers = {'Authorization': 'Bearer %s' % token, 'X-Dropbox-Perform-As-Team-Member': member_id} 4 | data = {'root': 'auto', 'from_path': from_path, 'to_path': to_path} 5 | 6 | print 'Moving "%s" to "%s" (member_id: %s)' % (from_path, to_path, member_id) 7 | 8 | r = requests.post(url, headers=headers, data=data) 9 | 10 | if r.status_code == 200: 11 | print 'Success!' 12 | return True 13 | else: 14 | print 'HTTP error %s - %s (%s)' % (r.status_code, r.reason, r.text) 15 | return False -------------------------------------------------------------------------------- /Files/QuotaAlert.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import print_function 3 | from __future__ import division 4 | import json 5 | import argparse 6 | import csv 7 | from urllib2 import Request, urlopen, HTTPError 8 | from multiprocessing.dummy import Pool as ThreadPool 9 | import sys 10 | reload(sys) 11 | sys.setdefaultencoding('UTF8') # @UndefinedVariable 12 | 13 | ''' 14 | QuotaAlert.py 15 | Scan all members of a Dropbox team and report on which users are near, 16 | or over, a given per-user quota 17 | REQUIRES: Team member file access permission 18 | ''' 19 | UNITS = {"MB": 1024 ** 2, "GB": 1024 ** 3, "TB": 1024 ** 4} 20 | 21 | dbxApiV2 = None 22 | 23 | parser = argparse.ArgumentParser( 24 | description='Checks team member disk usage and reports on users near, or over, quota') 25 | parser.add_argument('-o', '--output', nargs='?', default=sys.stdout, 26 | type=argparse.FileType('wb'), 27 | help='path to output file for CSV generation, default: stdout') 28 | parser.add_argument('-q', '--quota', nargs='?', default=1000, type=int, 29 | help='quota to check usage against, in units given with -u, default:1TB') 30 | parser.add_argument('-u', '--units', nargs='?', choices=['MB', 'GB', 'TB'], 31 | default='GB', help='unit value for quota, must be one of MB, GB, TB, default: GB') 32 | parser.add_argument('-w', '--warn', nargs='?', default=80, type=int, 33 | help='warning threshold, as a percentage of the quota, default: 80') 34 | parser.add_argument("-l", "--limit", nargs='?', default=1000, type=int, 35 | help='limit max records returned per Dropbox API call, default: 1000') 36 | parser.add_argument("-t", "--threads", nargs='?', default=4, type=int, 37 | help="worker thread count, default: 4") 38 | parser.add_argument("-v", "--verbose", action="store_const", 39 | default=False, const=True, help='enable verbose output') 40 | args = parser.parse_args() 41 | 42 | def main(): 43 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 44 | 45 | if args.verbose: 46 | dumpArguments() 47 | 48 | fileQuota = args.quota * UNITS[args.units] 49 | warnQuota = fileQuota * (args.warn / 100.0) 50 | 51 | log("Creating Dropbox V2 API Client") 52 | global dbxApiV2 53 | dbxApiV2 = DbxApi(DbxApi.DBX_API_V2, dfbToken) 54 | 55 | log("Collecting Member List...") 56 | members = getDfbMembers(None) 57 | # Filter out invited members as they can't consume any quota yet 58 | activeMembers = [member for member in members if member.status != "invited"] 59 | log("Got {} total team members ({} active, {} suspended, {} invited)" 60 | .format( 61 | len(members), len(activeMembers), 62 | len(getMemberSublist(members, "suspended")), 63 | len(getMemberSublist(members, "invited")) 64 | )) 65 | 66 | log("Collecting quota information - this may take a while...") 67 | pool = ThreadPool(args.threads) 68 | members = pool.map(getQuotaUsage, activeMembers) 69 | pool.close() 70 | pool.join() 71 | 72 | # Determine which users are near, or beyond, the quota value provided 73 | log("Checking for quota violations...") 74 | alertMembers = [] 75 | members.sort(key=lambda mbr: mbr.quotaUsed, reverse=True) 76 | for member in members: 77 | memberUsage = member.quotaUsed / UNITS["GB"] 78 | if member.quotaUsed >= fileQuota: 79 | member.quotaStatus = Quota.VIOLATION 80 | alertMembers.append(member) 81 | log("Member {} ({}) is over their quota by {:,.2f}GB! ({:,.2f}GB of {:,.2f}GB)" 82 | .format( 83 | member.fullName, member.email, 84 | (member.quotaUsed - fileQuota) / UNITS["GB"], 85 | memberUsage, fileQuota / UNITS["GB"] 86 | )) 87 | elif member.quotaUsed >= warnQuota: 88 | member.quotaStatus = Quota.WARN 89 | alertMembers.append(member) 90 | log("Member {} ({}) is above {}% of their max quota! ({:,.2f}GB of {:,.2f}GB)" 91 | .format( 92 | member.fullName, member.email, args.warn, memberUsage, 93 | fileQuota / UNITS["GB"] 94 | )) 95 | 96 | # Write final output 97 | log("Processing complete, writing output to {}".format(args.output.name)) 98 | dumpCsvFile(alertMembers) 99 | 100 | def getDfbMembers(cursor): 101 | """Get a list of all Dropbox for Business team members""" 102 | if cursor is not None: 103 | data = {"cursor": cursor} 104 | endpoint = "/team/members/list/continue" 105 | else: 106 | data = {"limit": args.limit} 107 | endpoint = "/team/members/list" 108 | 109 | try: 110 | result = dbxApiV2.call(endpoint, None, json.dumps(data)) 111 | members = listToMemberObj(result["members"]) 112 | 113 | # Check to see if we got all team members, if not, get the rest 114 | if result["has_more"]: 115 | members = members + getDfbMembers(result["cursor"]) 116 | 117 | return members 118 | except HTTPError as error: 119 | parser.error(error.read()) 120 | 121 | def getQuotaUsage(member): 122 | """Get disk usage information for a Dropbox for Business team member""" 123 | try: 124 | usage = dbxApiV2.call("/users/get_space_usage", member.id) 125 | # Populate the member object with the usage info 126 | member.quotaUsed = usage["used"] 127 | member.quotaAllocated = usage["allocation"]["allocated"] 128 | member.quotaType = usage["allocation"][".tag"] 129 | member.teamQuotaUsed = usage["allocation"]["used"] 130 | return member 131 | except HTTPError as error: 132 | status = json.loads(error.read()) 133 | if status["error"][".tag"] == "invalid_select_user": 134 | log("Failed to retrieve quota information for {} ({}), current status: {}" 135 | .format(member.fullName, member.email, member.status), True) 136 | member.quotaUsed = 0 137 | member.quotaAllocated = 0 138 | member.quotaType = 0 139 | member.teamQuotaUsed = 0 140 | return member 141 | 142 | parser.error(status) 143 | 144 | def dumpCsvFile(members): 145 | """Write member information to a CSV file""" 146 | if args.output == sys.stdout: 147 | log("-------------------------- BEGIN CSV OUTPUT --------------------------") 148 | 149 | csvwriter = csv.writer(args.output) 150 | csvwriter.writerow(['First Name', 151 | 'Last Name', 152 | 'Email', 153 | 'Account Status', 154 | 'Quota Status', 155 | 'Quota Usage (bytes)', 156 | ]) 157 | for member in members: 158 | csvwriter.writerow([member.firstName, 159 | member.lastName, 160 | member.email, 161 | member.status, 162 | member.quotaStatus, 163 | str(member.quotaUsed) 164 | ]) 165 | 166 | def listToMemberObj(memberList): 167 | """Convert a list of member info dicts into a list of Member Class objects""" 168 | members = [] 169 | for member in memberList: 170 | members.append(Member(member)) 171 | return members 172 | 173 | def getMemberSublist(members, status): 174 | sublist = [] 175 | for member in members: 176 | if member.status == status: 177 | sublist.append(member) 178 | return sublist 179 | 180 | def log(msg, isError=False): 181 | """Log information to stdout, or stderr based upon global verbosity setting""" 182 | if isError: 183 | print(msg, file=sys.stderr) 184 | return 185 | if args.verbose: 186 | print(msg) 187 | 188 | def dumpArguments(): 189 | log("Verbose output enabled") 190 | log("Output file set to {}".format(args.output.name)) 191 | log("Quota set to {}{}".format(args.quota, args.units)) 192 | log("Warning threshold set to {}%".format(args.warn)) 193 | log("Max records set to {}".format(args.limit)) 194 | log("Worker threads set to {}".format(args.threads)) 195 | 196 | 197 | class DbxApi: 198 | """DbxApi - Convenience wrapper class around Dropbox API calls""" 199 | DBX_API_V1 = "https://api.dropbox.com/1" 200 | DBX_API_V2 = "https://api.dropboxapi.com/2" 201 | 202 | def __init__(self, baseUrl, accessToken): 203 | self.baseUrl = baseUrl 204 | self.accessToken = accessToken 205 | 206 | def call(self, endpoint, mbrId=None, payload=None, setContent=True): 207 | if payload is not None: 208 | payload = payload.encode('utf8') 209 | request = Request(self.baseUrl + endpoint, payload) 210 | request.add_header("Content-type", 'application/json') 211 | else: 212 | request = Request(self.baseUrl + endpoint) 213 | request.add_header("Authorization", "Bearer " + self.accessToken) 214 | request.get_method = lambda: 'POST' 215 | 216 | if mbrId is not None: 217 | if self.baseUrl == self.DBX_API_V2: 218 | request.add_header("Dropbox-API-Select-User", mbrId) 219 | else: 220 | request.add_header("X-Dropbox-Perform-As-Team-Member", mbrId) 221 | 222 | try: 223 | return json.loads(urlopen(request).read()) 224 | except HTTPError: 225 | # raise exception to caller. 226 | raise 227 | 228 | class Member: 229 | """Member - Convenience wrapper class around a Dropbox for Business team member""" 230 | 231 | def __init__(self, member): 232 | self.firstName = member["profile"]["name"]["given_name"] 233 | self.lastName = member["profile"]["name"]["surname"] 234 | self.fullName = self.firstName + " " + self.lastName 235 | self.id = member["profile"]["team_member_id"] 236 | self.email = member["profile"]["email"] 237 | self.status = member["profile"]["status"][".tag"] 238 | self.quotaStatus = Quota.NORMAL 239 | # Quota values won't be present until getQuotaUsage() is called! 240 | self.quotaUsed = None 241 | self.quotaAllocated = None 242 | self.quotaType = None 243 | self.teamQuotaUsed = None 244 | 245 | class Quota: 246 | """Enum for Quota status constants""" 247 | NORMAL = "NORMAL" 248 | WARN = "WARN" 249 | VIOLATION = "VIOLATION" 250 | 251 | if __name__ == '__main__': 252 | main() 253 | -------------------------------------------------------------------------------- /Files/SearchTeamFiles.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import json 4 | import argparse 5 | import sys 6 | import csv 7 | from collections import Counter 8 | 9 | reload(sys) 10 | sys.setdefaultencoding('UTF8') 11 | 12 | parser = argparse.ArgumentParser(description='Search for files in the DfB team.') 13 | parser.add_argument('query', help='Search Query.') 14 | parser.add_argument('-u', '--user', dest='users', action='append', help='Target user (email address) to scan. All team members will be returned if unspecified. You may pass multiple -u arguments.') 15 | parser.add_argument('-m', '--mode', default='filename_and_content', dest='mode', help='Search mode. Valid options are filename_and_content, filename, or deleted_filename.') 16 | 17 | args = parser.parse_args() 18 | 19 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 20 | 21 | #Look up a DfB member from an email address 22 | def getDfbMember(email): 23 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/get_info', json.dumps({'email':email})) 24 | request.add_header("Authorization", "Bearer "+dfbToken) 25 | request.add_header("Content-type", 'application/json') 26 | try: 27 | return json.loads(urllib2.urlopen(request).read()) 28 | 29 | # Exit on error here. Probably user not found or bad OAuth token. Show DfB response. 30 | except urllib2.HTTPError, error: 31 | print ( "ERROR: " + error.read() ) 32 | parser.error(error.read()) 33 | 34 | 35 | # Get all DfB members, paging through member list if necessary 36 | def getDfbMembers(cursor): 37 | data = {"limit":100} 38 | if cursor is not None: 39 | data["cursor"] = cursor 40 | 41 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/list', json.dumps(data)) 42 | request.add_header("Authorization", "Bearer "+dfbToken) 43 | request.add_header("Content-type", 'application/json') 44 | try: 45 | response = json.loads(urllib2.urlopen(request).read()) 46 | members = response["members"] 47 | 48 | if response["has_more"]: 49 | members = members + getDfbMembers(cursor=response["cursor"]) 50 | 51 | return members 52 | 53 | # Exit on error here. Probably bad OAuth token. Show DfB response. 54 | except urllib2.HTTPError, error: 55 | parser.error(error.read()) 56 | 57 | # Searches a member's dropbox, paging through the results if necessary 58 | def searchFiles(memberEmail, memberId, csvwriter): 59 | cursor = None 60 | num_files = 0 61 | start = 0 62 | 63 | try: 64 | while True: 65 | params = {"start":start, "path":"", "mode":args.mode, "max_results":2, "query":args.query} 66 | 67 | request = urllib2.Request('https://api.dropboxapi.com/2/files/search', data=json.dumps(params)) 68 | request.add_header("Authorization", "Bearer "+dfbToken) 69 | request.add_header("Dropbox-API-Select-User", memberId) 70 | request.add_header("Content-type", 'application/json') 71 | 72 | response_string = urllib2.urlopen(request).read() 73 | response = json.loads(response_string) 74 | 75 | for match in response["matches"]: 76 | metadata = match["metadata"] 77 | csvwriter.writerow([memberEmail, metadata["path_lower"], metadata[".tag"], match["match_type"][".tag"],\ 78 | metadata["size"] if 'size' in metadata else '-', \ 79 | formatSize(metadata["size"]) if 'size' in metadata else '-', \ 80 | metadata["server_modified"] if 'server_modified' in metadata else '-']) 81 | num_files = num_files + 1 82 | 83 | start = response['start'] 84 | 85 | if not response['more']: 86 | break 87 | 88 | sys.stderr.write(" found {} matches for {} \n".format(num_files, memberEmail)) 89 | 90 | except urllib2.HTTPError as error: 91 | parser.error(error.read()) 92 | 93 | 94 | def formatSize(num, suffix='B'): 95 | for unit in ['','K','M','G','T','P','E','Z']: 96 | if abs(num) < 1000.0: 97 | return "%3.1f%s%s" % (num, unit, suffix) 98 | num /= 1024.0 99 | return "%.1f%s%s" % (num, 'Yi', suffix) 100 | 101 | 102 | members = [] 103 | if (args.users is not None): 104 | members = map(getDfbMember, args.users) 105 | else: 106 | members = getDfbMembers(None) 107 | 108 | csvwriter = csv.writer(sys.stdout) 109 | 110 | csvwriter.writerow(['User', 'Path', 'Type', 'Match Type', 'Size (bytes)', 'Size (formatted)', 'Modified']) 111 | 112 | 113 | #TODO: Thread this? 114 | for member in members: 115 | if member["profile"]["status"][".tag"] == "active": 116 | files = searchFiles(member["profile"]["email"], member["profile"]["team_member_id"], csvwriter) 117 | -------------------------------------------------------------------------------- /Files/TeamUsageStatistics.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import json 4 | import argparse 5 | import sys 6 | import csv 7 | import time 8 | from collections import Counter 9 | 10 | reload(sys) 11 | sys.setdefaultencoding('UTF8') 12 | 13 | parser = argparse.ArgumentParser(description='Lists advanced aggregate file stats of the DfB team.') 14 | parser.add_argument('-u', '--user', dest='users', action='append', help='Target user (email address) to scan. All team members will be returned if unspecified. You may pass multiple -u arguments.') 15 | 16 | args = parser.parse_args() 17 | 18 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 19 | 20 | #Look up a DfB member from an email address 21 | def getDfbMember(email): 22 | request = urllib2.Request('https://api.dropbox.com/1/team/members/get_info', json.dumps({'email':email})) 23 | request.add_header("Authorization", "Bearer "+dfbToken) 24 | request.add_header("Content-type", 'application/json') 25 | try: 26 | return json.loads(urllib2.urlopen(request).read()) 27 | 28 | # Exit on error here. Probably user not found or bad OAuth token. Show DfB response. 29 | except urllib2.HTTPError, error: 30 | parser.error(error.read()); 31 | 32 | 33 | # Get all DfB members, paging through results if necessary 34 | def getDfbMembers(cursor): 35 | data = {"limit":100} 36 | if cursor is not None: 37 | data["cursor"] = cursor 38 | 39 | request = urllib2.Request('https://api.dropbox.com/1/team/members/list', json.dumps(data)) 40 | request.add_header("Authorization", "Bearer "+dfbToken) 41 | request.add_header("Content-type", 'application/json') 42 | try: 43 | response = json.loads(urllib2.urlopen(request).read()) 44 | members = response["members"] 45 | 46 | if response["has_more"]: 47 | members = members + getDfbMembers(cursor=response["cursor"]) 48 | 49 | return members 50 | 51 | # Exit on error here. Probably bad OAuth token. Show DfB response. 52 | except urllib2.HTTPError, error: 53 | parser.error(error.read()) 54 | 55 | # Get a member's info (account details, quota usage) 56 | def getMemberInfo(memberId): 57 | request = urllib2.Request('https://api.dropboxapi.com/1/account/info') 58 | request.add_header("Authorization", "Bearer "+dfbToken) 59 | request.add_header("Content-type", 'application/json') 60 | request.add_header("X-Dropbox-Perform-As-Team-Member", memberId) 61 | 62 | try: 63 | return json.loads(urllib2.urlopen(request).read()) 64 | except urllib2.HTTPError, error: 65 | print " DfB ERROR: "+error.read() 66 | 67 | # Get all file metadata, counting files/folders/shares & noting last modification time 68 | def countFiles(memberEmail, memberId, csvwriter): 69 | 70 | lastModTime = None; 71 | files = Counter({'shared_folders':0, 'shared_files':0, 'shared_bytes':0, 'private_folders':0, 'private_files':0, 'private_bytes':0}) 72 | cursor = None 73 | 74 | try: 75 | while True: 76 | params = {} 77 | if cursor is not None: 78 | params['cursor'] = cursor 79 | request = urllib2.Request('https://api.dropboxapi.com/1/delta', data=urllib.urlencode(params)) 80 | request.add_header("Authorization", "Bearer "+dfbToken) 81 | request.add_header("X-Dropbox-Perform-As-Team-Member", memberId) 82 | 83 | response_string = urllib2.urlopen(request).read() 84 | response = json.loads(response_string) 85 | 86 | for path, md in response["entries"]: 87 | if md is None: 88 | pass # Delete entry. Skip it. 89 | else: 90 | 91 | shared = False 92 | if 'parent_shared_folder_id' in md or 'shared_folder' in md: 93 | shared = True 94 | 95 | # Look for last time file was modified by the user (private file, or shared & modified by user) 96 | if (shared == False) or (md["modifier"] is not None and md["modifier"]["email"] == memberEmail): 97 | modTime = time.strptime(md["modified"][:-6], "%a, %d %b %Y %H:%M:%S") 98 | if (lastModTime is None or modTime > lastModTime): 99 | lastModTime = modTime 100 | 101 | # Count the folder 102 | if (md["is_dir"]): 103 | if (shared): 104 | files = files + Counter({'shared_folders':1}) 105 | else: 106 | files = files + Counter({'private_folders':1}) 107 | # Count the file 108 | else: 109 | if (shared): 110 | files = files + Counter({'shared_files':1, 'shared_bytes':md["bytes"]}) 111 | else: 112 | files = files + Counter({'private_files':1, 'private_bytes':md["bytes"]}) 113 | 114 | if response["reset"] and cursor is not None: 115 | sys.stderr.write(" ERROR: got a reset!") 116 | csvwriter.writerow([memberEmail, "/delta with cursor={!r} returned RESET".format(cursor), "ERROR", "-", "-", "-", "-", "-", "-", "-", "-"]) 117 | break 118 | 119 | if not response['has_more']: 120 | break 121 | 122 | cursor = response['cursor'] 123 | 124 | csvwriter.writerow([memberEmail, str(files["shared_bytes"]), formatSize(files["shared_bytes"]), str(files["shared_files"]), str(files["shared_folders"]), countSharedLinks(memberId),\ 125 | str(files["private_bytes"]), formatSize(files["private_bytes"]), str(files["private_files"]), str(files["private_folders"]), time.strftime('%Y-%m-%d %H:%M:%S', lastModTime)]) 126 | 127 | except urllib2.HTTPError as error: 128 | print error.read() 129 | csvwriter.writerow([memberEmail, "ERROR", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"]) 130 | sys.stderr.write(" ERROR: {}\n".format(error)) 131 | 132 | 133 | def countSharedLinks(memberId): 134 | cursor = None 135 | count = 0 136 | 137 | while True: 138 | params = {} 139 | if cursor is not None: 140 | params['cursor'] = cursor 141 | request = urllib2.Request('https://api.dropboxapi.com/2/sharing/list_shared_links', json.dumps(params)) 142 | request.add_header("Authorization", "Bearer "+dfbToken) 143 | request.add_header("Dropbox-API-Select-User", memberId) 144 | request.add_header("Content-Type", "application/json") 145 | 146 | response_string = urllib2.urlopen(request).read() 147 | response = json.loads(response_string) 148 | count = count + len(response["links"]) 149 | 150 | if not response['has_more']: 151 | break 152 | cursor = response['cursor'] 153 | 154 | return count 155 | 156 | 157 | 158 | def formatSize(num, suffix='B'): 159 | for unit in ['','K','M','G','T','P','E','Z']: 160 | if abs(num) < 1000.0: 161 | return "%3.1f%s%s" % (num, unit, suffix) 162 | num /= 1024.0 163 | return "%.1f%s%s" % (num, 'Yi', suffix) 164 | 165 | 166 | members = [] 167 | if (args.users is not None): 168 | members = map(getDfbMember, args.users) 169 | else: 170 | members = getDfbMembers(None) 171 | 172 | csvwriter = csv.writer(sys.stdout) 173 | csvwriter.writerow(['Email','Shared Bytes','Shared Size','Shared Files','Shared Folders','Shared Links','Private Bytes','Private Size','Private Files','Private Folders','Last File Mod Time']) 174 | 175 | for member in members: 176 | if member["profile"]["status"] == "active": 177 | countFiles(member["profile"]["email"], member["profile"]["member_id"], csvwriter) 178 | -------------------------------------------------------------------------------- /Groups/ListGroupFolderPermissions.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import argparse 4 | import sys 5 | import csv 6 | 7 | reload(sys) 8 | sys.setdefaultencoding('UTF8') 9 | 10 | parser = argparse.ArgumentParser(description='Lists all folders and folder permissions for groups in a DB or DE team.') 11 | parser.add_argument('-g', '--group', dest='groups', action='append', help='Target group name to scan. All groups will ' 12 | 'be scanned be returned if unspecified. You ' 13 | 'may pass multiple -g arguments.') 14 | args = parser.parse_args() 15 | 16 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 17 | 18 | 19 | # Get all DfB Groups 20 | def get_groups(): 21 | request = urllib2.Request('https://api.dropbox.com/1/team/groups/list', json.dumps({})) 22 | request.add_header("Authorization", "Bearer "+dfbToken) 23 | request.add_header("Content-type", 'application/json') 24 | try: 25 | response = json.loads(urllib2.urlopen(request).read()) 26 | return response["groups"] 27 | # Exit on error here. Probably bad OAuth token. Show DfB response. 28 | except urllib2.HTTPError, error: 29 | parser.error(error.read()) 30 | 31 | 32 | # Return member id of the first member that belongs to the specified group 33 | def get_first_group_member(group_id): 34 | data = {"group_ids": [group_id]} 35 | request = urllib2.Request('https://api.dropbox.com/1/team/groups/get_info', json.dumps(data)) 36 | request.add_header("Authorization", "Bearer "+dfbToken) 37 | request.add_header("Content-type", 'application/json') 38 | try: 39 | response = json.loads(urllib2.urlopen(request).read()) 40 | return response["groups"][0]["members"][0]["profile"]["member_id"] 41 | except urllib2.HTTPError, error: 42 | parser.error(error.read()) 43 | 44 | 45 | # Find all folders in a particular group (by searching for a member & including unmounted folders) 46 | # will also print groups that the first user found of the anchor group is a part of and flag them as checked 47 | def get_group_folders(csv_writer, checked, anchor_group_id): 48 | 49 | checking = {anchor_group_id: []} 50 | 51 | try: 52 | request = urllib2.Request('https://api.dropbox.com/1/shared_folders' 53 | '?include_membership=true&show_unmounted=true') 54 | request.add_header("Authorization", "Bearer "+dfbToken) 55 | request.add_header("X-Dropbox-Perform-As-Team-Member", get_first_group_member(anchor_group_id)) 56 | response_string = urllib2.urlopen(request).read() 57 | response = json.loads(response_string) 58 | 59 | # for all groups that each shared folder has access to, add line to print to each group's folders array 60 | for folder in response: 61 | # for each group that has access to a folder 62 | for folder_group in folder['groups']: 63 | folder_group_id = folder_group['group']['id'] 64 | 65 | # verify that the group is in the list of currently active and inhabited groups 66 | # and the group hasn't already been checked/printed out 67 | if folder_group_id in checked and checked[folder_group_id] is False: 68 | 69 | # if this group passes those but isn't already being tracked for this user, 70 | # add it to our checking list 71 | if folder_group_id not in checking: 72 | checking[folder_group_id] = [] 73 | 74 | # log the folder in the list of folders this group has access to 75 | checking[folder_group_id].append([ 76 | folder_group['group']['display_name'].encode("utf-8"), 77 | folder_group['access_type'], 78 | folder['shared_folder_id'], 79 | folder["owner"]["display_name"], 80 | folder['shared_folder_name'].encode("utf-8") 81 | ]) 82 | 83 | except urllib2.HTTPError as error: 84 | sys.stderr.write(" ERROR: {}\n".format(error)) 85 | 86 | # flip the checked flag to true and print out folders by group 87 | for g in checking: 88 | checked[g] = True 89 | for f in checking[g]: 90 | csv_writer.writerow(f) 91 | 92 | csv_writer = csv.writer(sys.stdout) 93 | csv_writer.writerow(['Group Name', 'Group Access', 'Shared Folder Id', 'Shared Owner', 'Shared Folder Name']) 94 | 95 | # find dfb groups 96 | groups = get_groups() 97 | 98 | # create a dictionary flagging if a group was checked from a previous group's first member 99 | checkedGroups = dict() 100 | 101 | # validate user entry of groups (if applicable) - either add just the specified groups as checking, else add all groups 102 | if args.groups is not None: 103 | groupNames = dict() 104 | for group in groups: 105 | groupNames[group['group_name']] = group['group_id'] 106 | 107 | for group in args.groups: 108 | if group not in groupNames: 109 | parser.error("Group " + group + " does not exist") 110 | else: 111 | checkedGroups[group['group_id']] = False 112 | else: 113 | for group in groups: 114 | checkedGroups[group['group_id']] = False 115 | 116 | # print folders for each group, so long as they have members and haven't been checked yet 117 | for group in groups: 118 | if (args.groups is None or group["group_name"] in args.groups) and \ 119 | group["num_members"] > 0 and checkedGroups[group["group_id"]] is False: 120 | get_group_folders(csv_writer, checkedGroups, group["group_id"]) 121 | -------------------------------------------------------------------------------- /Groups/createGroupWithAllTeamMembers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | 4 | import json 5 | import requests 6 | import os # Allows for the clearing of the Terminal Window 7 | import csv # Allows outputting to CSV file 8 | 9 | 10 | """ 11 | A Script to create a group called 'GRP_ALL_TEAM_MEMBERS', and iterate over all members of a team, adding all member to the group. 12 | 13 | Note: 14 | This example assumes small number of users in team. If you had a large number of members you might want to make multiple calls 15 | with batches of users and a delay built in to allow time to process the request. 16 | 17 | 18 | Requirements: 19 | Script writen testing on Python 3.6.5 20 | 21 | Dropbox API Token needed inserted just below this comments section. 22 | The following scoped permissions needed: 23 | * team_data.member 24 | * members.read 25 | * groups.write 26 | 27 | Pre-requisites: 28 | * Scripts requires library 'Requests' - You can install using "pip install requests" 29 | 30 | """ 31 | 32 | """ 33 | Set your OAuth Tokens here 34 | """ 35 | gScopedToken = '' # API Scoped Token 36 | 37 | gGroupName = 'GRP_ALL_TEAM_MEMBERS' 38 | 39 | 40 | 41 | 42 | """ 43 | DO NOT EDIT BELOW THIS POINT 44 | """ 45 | 46 | 47 | 48 | """ 49 | # ############################################ 50 | # Step 0 51 | # Clear the terminal window, not essential but makes it easier to read this way. 52 | # ############################################ 53 | """ 54 | 55 | os.system('cls' if os.name=='nt' else 'clear') 56 | 57 | 58 | """ 59 | # ############################################ 60 | # Step 1 61 | # 1. Check if we have the necessary Tokens. 62 | # 2. If not, ask the user to enter it. 63 | # ############################################ 64 | """ 65 | if (gScopedToken == ''): 66 | gScopedToken = raw_input('Enter your Dropbox Business API App token: ') 67 | 68 | aHeaders = {'Content-Type': 'application/json', 69 | 'Authorization': 'Bearer %s' % gScopedToken} 70 | 71 | """ 72 | ############################################# 73 | # Step 1 74 | # 1. Setup the necessary variables to get list of members. 75 | ############################################# 76 | """ 77 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 78 | aData = json.dumps({'limit': 300}) 79 | 80 | 81 | 82 | """ 83 | ############################################# 84 | # Step 2 85 | # 1. Get list of all Dropbox Team Members 86 | # 2. Create in memory list of them. 87 | ############################################# 88 | """ 89 | hasMore = True; # Controls how long we stay in while loop loading users. 90 | loopCounter = 0 # Count of how many times we hit the API 91 | dbxUsers = [] # List of Dropbox Users 92 | dbxMembers = [] 93 | 94 | print ("> Retrieving Dropbox Users via API") 95 | 96 | while hasMore: 97 | 98 | print (">>> API call") 99 | """ Make the API call """ 100 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 101 | 102 | print ("<<< Results") 103 | 104 | # If we don't get a 200 HTML response code, we didn't get a result. 105 | if( aResult.status_code != 200 ): 106 | print ('>>> Failed to get a response to call for /team/members/list') 107 | print (aResult.text) 108 | exit(); 109 | 110 | # Note the JSON response 111 | members = aResult.json() 112 | 113 | # Iterate over the Members in the JSON 114 | for aMember in members['members']: 115 | dbxUsers.append( aMember ) 116 | memDetails = {"user": {".tag":"team_member_id", "team_member_id": aMember['profile']['team_member_id']},"access_type": "member"} 117 | dbxMembers.append( memDetails ) 118 | 119 | hasMore = members['has_more'] # Note if there's another cursor call to make. 120 | 121 | # If it's the first run, from this point onwards the API call is the /continue version. 122 | if ( loopCounter >= 0 ): 123 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 124 | aData = json.dumps({'cursor': members['cursor']}) 125 | loopCounter += 1 126 | 127 | print (" We have " + str(len(dbxUsers)) + " Dropbox Team members in memory") 128 | 129 | 130 | 131 | """ 132 | ############################################# 133 | # Step 3 134 | # 1. Create a Group based on variable gGroupName 135 | ############################################# 136 | """ 137 | 138 | aURL = 'https://api.dropboxapi.com/2/team/groups/create' 139 | aData = json.dumps({'group_name': gGroupName, 'group_management_type': 'company_managed'}) 140 | 141 | 142 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 143 | 144 | # If we don't get a 200 HTML response code, we didn't get a result. 145 | if( aResult.status_code != 200 ): 146 | print ('>>> Failed to create a group using /2/team/groups/create') 147 | print (aResult.text) 148 | exit(); 149 | 150 | # Note the JSON response 151 | group = aResult.json() 152 | 153 | # Note the Group ID 154 | aGroupID = group['group_id'] 155 | 156 | 157 | """ 158 | ############################################# 159 | # Step 3 160 | # 1. Create a Group based on variable gGroupName 161 | ############################################# 162 | """ 163 | 164 | aURL = 'https://api.dropboxapi.com/2/team/groups/members/add' 165 | aData = json.dumps({"group": {".tag": "group_id", "group_id": aGroupID}, "members": dbxMembers, "return_members": False}) 166 | 167 | 168 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 169 | 170 | # If we don't get a 200 HTML response code, we didn't get a result. 171 | if( aResult.status_code != 200 ): 172 | print ('>>> Failed to add members to group using /2/team/groups/members/add') 173 | print (aResult.text) 174 | exit(); 175 | 176 | 177 | print ( 'All members added to Group') 178 | print ('\n\n\nExiting Script.') 179 | -------------------------------------------------------------------------------- /Groups/createGroupsFromCSV.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import os # Allows for the clearing of the Terminal Window 4 | import csv # Allows outputting to CSV file 5 | import time, datetime 6 | 7 | 8 | """ 9 | ******************************************************************************************************************** 10 | 11 | The intention of this script is to: 12 | 13 | Load a CSV file of groups to be created into a Dropbox Team account. 14 | The script assumes the file is in the same location as the executing python script. 15 | 16 | You need to provide three inputs into the variables below. 17 | 1. Team Member Management token 18 | 2. The name of the source file 19 | 3. Whether the groups are created as 'company_managed' or 'user_managed' groups 20 | user_managed = A group which is managed by selected users. 21 | company_managed = A group which is managed by team admins only. 22 | 4. If the script tried to create a group that already exists in Dropbox, it will note it and output all failures 23 | to a csv file called 'failedToCreateGroups.csv' 24 | 25 | 26 | ******************************************************************************************************************** 27 | """ 28 | 29 | 30 | 31 | 32 | """ 33 | Set your OAuth Token here with 'Team Member Management' permissions 34 | """ 35 | gTokenTMM = '' # Team Member Management 36 | gSourceFile = 'groups.csv' # source file with names of groups to create 37 | gGroupManagementType = 'company_managed' # user_managed = A group which is managed by selected users. 38 | 39 | 40 | 41 | 42 | 43 | 44 | """ 45 | ******************************************************************************************************************** 46 | DO NOT EDIT BELOW THIS POINT 47 | ******************************************************************************************************************** 48 | """ 49 | 50 | # Note the time we start the script 51 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 52 | 53 | ############################################# 54 | # Function to print Message to console in a tidy box 55 | ############################################# 56 | def printTimeInHoursMinutesSeconds( sec ): 57 | sec = int(sec) 58 | hrs = sec / 3600 59 | sec -= 3600*hrs 60 | 61 | mins = sec / 60 62 | sec -= 60*mins 63 | 64 | return '%s hrs, %s mins, %s sec' % ( hrs, mins, sec); 65 | 66 | 67 | ############################################# 68 | # Step 0 69 | # Clear the terminal window, not essential but makes it easier to read this way. 70 | ############################################# 71 | 72 | os.system('cls' if os.name=='nt' else 'clear') 73 | 74 | 75 | 76 | ############################################# 77 | # Step 1 78 | # Check file exists to ensure there's something to work off 79 | ############################################# 80 | 81 | bHaveXLSX = os.path.isfile( gSourceFile ) 82 | 83 | if ( bHaveXLSX is False ): 84 | print('>>> Can not find the source file %s, exiting the script.\n' % gSourceFile); 85 | exit(); 86 | 87 | 88 | 89 | ############################################# 90 | # Step 2 91 | # Open the source file. 92 | # For each row it the file create a group in Dropbox Account 93 | ############################################# 94 | 95 | createdGroupsCnt = 0 96 | failedGroups = [] # List of Groups we couldn't create 97 | 98 | aURL = 'https://api.dropboxapi.com/2/team/groups/create' 99 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % gTokenTMM} 100 | 101 | with open( gSourceFile, 'rb') as csvfileRead: 102 | # Open file to read from 103 | reader = csv.reader(csvfileRead) 104 | 105 | #Iterate through each row of the CSV. 106 | for row in reader: 107 | 108 | # Set up the call to create new group name 109 | aData = json.dumps({'group_name': row[0], 'group_management_type': gGroupManagementType}) 110 | 111 | print ('>>> API call - Create Group: %s' % row[0]) 112 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 113 | print ('<<< Results') 114 | 115 | if ( aResult.status_code == 200): 116 | createdGroupsCnt += 1 117 | else: 118 | # If we get a 409 HTML response code, group already exists. 119 | if( aResult.status_code == 409 ): 120 | failedGroups.append( row[0] ) 121 | else: 122 | print ('* Failed with call to Create Group "%s". \nWe got an error [%s] with text "%s"' % (row[0], aResult.status_code, aResult.text)) 123 | 124 | 125 | ############################################# 126 | # Step 3 127 | # If we got failures to create, output to a csv file. 128 | ############################################# 129 | 130 | if ( len(failedGroups) > 0 ): 131 | 132 | with open( 'failedToCreateGroups.csv', 'wt') as csvfile: 133 | 134 | # Define the delimiter 135 | writer = csv.writer(csvfile, delimiter=',') 136 | 137 | for item in failedGroups: 138 | writer.writerow( [item] ) 139 | 140 | 141 | """ 142 | ############################################# 143 | # Step 4 144 | # Output how long the script took to run. 145 | ############################################# 146 | """ 147 | 148 | print( '\n\n%s groups cretaed.' % createdGroupsCnt) 149 | print( '%s groups failed to create ' % len(failedGroups) ) 150 | 151 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 152 | totalTimeInSeconds = (totalTimeStop-totalTimeStart).total_seconds() 153 | timeAsStr = printTimeInHoursMinutesSeconds( totalTimeInSeconds ) 154 | print( "\n\nScript finished running, it took %s seconds." % ( timeAsStr ) ) 155 | -------------------------------------------------------------------------------- /Groups/getGroupMembers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | 4 | import json 5 | import requests 6 | import pprint # Allows Pretty Print of JSON 7 | import os # Allows for the clearing of the Terminal Window 8 | import csv # Allows outputting to CSV file 9 | import time, datetime 10 | import sys 11 | 12 | 13 | """ 14 | Example script to pull out a report on Groups and their members. 15 | Script is limited to report on 1000 groups and 1000 members per group. 16 | 17 | It will generate two files: 18 | - groups.csv 19 | - member-groups.csv 20 | 21 | groups.csv: List of groups, management type, member count, and all member emails list thereafter 22 | member-groups.csv: email address of member and group name. 23 | 24 | 25 | Requirements: 26 | Script tested on Python 3.6.5 27 | 28 | One Dropbox API Token is needed, inserted just below this comments section. 29 | * Team Information 30 | 31 | Pre-requisites: 32 | * Scripts requires library 'Requests' - You can install using "pip install requests" 33 | 34 | """ 35 | 36 | 37 | 38 | 39 | 40 | """ 41 | Set your OAuth Tokens here 42 | """ 43 | gTokenTI = '' # Team Information App Token 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | """########################################################################################## 53 | DO NOT EDIT BELOW THIS POINT 54 | ##########################################################################################""" 55 | 56 | gTotalTimeStart = datetime.datetime.fromtimestamp(time.time()) 57 | 58 | 59 | ############################################# 60 | # Function to print Message to console in a tidy box 61 | ############################################# 62 | def printmessageblock( str ): 63 | print ("\n*********************************************************") 64 | print ("* %s" % (str)) 65 | print ("*********************************************************\n") 66 | return; 67 | 68 | ############################################# 69 | # Function to return current Timestamp 70 | ############################################# 71 | def getTimeYMDHM(): 72 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M') 73 | return lRightNow; 74 | 75 | ############################################# 76 | # Function to return Message to console in a tidy box 77 | ############################################# 78 | def getTimeInHoursMinutesSeconds( sec ): 79 | return time.strftime("%H hrs %M mins %S sec", time.gmtime(sec)) 80 | 81 | ############################################# 82 | # Function to return a list of members for a group 83 | ############################################# 84 | def getGroupMembers ( group_id ): 85 | members_list = [] 86 | 87 | aHeaders = {'Content-Type': 'application/json', 88 | 'Authorization': 'Bearer %s' % gTokenTI} 89 | aURL = 'https://api.dropboxapi.com/2/team/groups/members/list' 90 | aData = json.dumps({'group':{'.tag':'group_id','group_id': group_id},'limit': 1000}) 91 | 92 | print (">>> API call to get Group Members") 93 | 94 | """ Make the API call """ 95 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 96 | 97 | print ("<<< Results") 98 | 99 | # If we don't get a 200 HTML response code, we didn't get a result. 100 | if( aResult.status_code != 200 ): 101 | print ('>>> Failed to get a response to call for /2/team/groups/members/list ') 102 | print (aResult.text) 103 | exit(); 104 | 105 | # Note the JSON response 106 | group_members = aResult.json() 107 | 108 | for member in group_members['members']: 109 | members_list.append( member['profile']['email']) 110 | 111 | return members_list 112 | 113 | 114 | 115 | 116 | """ 117 | ############################################# 118 | # Step 1 119 | # 1. Load groups, iterate over them 120 | ############################################# 121 | """ 122 | 123 | aHeaders = {'Content-Type': 'application/json', 124 | 'Authorization': 'Bearer %s' % gTokenTI} 125 | aURL = 'https://api.dropboxapi.com/2/team/groups/list' 126 | aData = json.dumps({'limit': 1000}) 127 | 128 | print (">>> API call to get Groups") 129 | 130 | """ Make the API call """ 131 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 132 | 133 | print ("<<< Results") 134 | 135 | # If we don't get a 200 HTML response code, we didn't get a result. 136 | if( aResult.status_code != 200 ): 137 | print ('>>> Failed to get a response to call for /2/team/groups/list ') 138 | print (aResult.text) 139 | exit(); 140 | 141 | # Note the JSON response 142 | teamGroups = aResult.json() 143 | 144 | with open( 'groups.csv', 'wt') as csvfile: 145 | # Define the delimiter 146 | writer = csv.writer(csvfile, delimiter=',') 147 | writer.writerow(['group name','management type', 'members count', 'members']) 148 | 149 | 150 | with open( 'member-groups.csv', 'wt') as csvmemberfile: 151 | # Define the delimiter 152 | memberwriter = csv.writer(csvmemberfile, delimiter=',') 153 | memberwriter.writerow(['member','group name']) 154 | 155 | 156 | for group in teamGroups['groups']: 157 | 158 | group_id = group['group_id'] 159 | group_name = group['group_name'] 160 | management_type = group['group_management_type']['.tag'] 161 | member_cnt = group['member_count'] 162 | members = [''] 163 | 164 | if ( member_cnt > 0 ): 165 | members = getGroupMembers( group_id ) 166 | 167 | for member in members: 168 | memberwriter.writerow([member,group_name]) 169 | 170 | myList = [group_name, management_type, str(member_cnt)] 171 | myList.extend( members ) 172 | 173 | writer.writerow(myList) 174 | 175 | 176 | """ 177 | ############################################# 178 | # Step 2 179 | # 1. Output how long the script took to run. 180 | ############################################# 181 | """ 182 | gTotalTimeStop = datetime.datetime.fromtimestamp(time.time()) 183 | gTotalTimeInSeconds = (gTotalTimeStop-gTotalTimeStart).total_seconds() 184 | timeAsStr = getTimeInHoursMinutesSeconds( gTotalTimeInSeconds ) 185 | printmessageblock( " Script finished running, it took %s seconds." % ( timeAsStr ) ) 186 | -------------------------------------------------------------------------------- /Integrations/ListDeviceSessions.py: -------------------------------------------------------------------------------- 1 | # Lists all device sessions 2 | 3 | import urllib2 4 | import json 5 | import argparse 6 | import sys 7 | import datetime 8 | import csv 9 | 10 | reload(sys) 11 | sys.setdefaultencoding('UTF8') 12 | csv_writer = csv.writer(sys.stdout) 13 | 14 | # Collect user input 15 | parser = argparse.ArgumentParser(description='Lists all linked devices / sessions in the Dropbox Business team. If ' 16 | 'web/mobile/desktop is not specified, all device sessions will be listed.') 17 | parser.add_argument('-u', '--user', dest='users', action='append', 18 | help='Target users (email address) to scan. All team members will be scanned if unspecified. ' 19 | 'You may pass multiple -u arguments.') 20 | parser.add_argument('-r', '--revoke', dest='revoke', action='store_true', default=False, 21 | help='Revoke all matching sessions.') 22 | parser.add_argument('-w', '--web', dest='web', action='store_true', default=False, 23 | help='Show web sessions.') 24 | parser.add_argument('-d', '--desktop', dest='desktop', action='store_true', default=False, 25 | help='Show desktop sessions.') 26 | parser.add_argument('-m', '--mobile', dest='mobile', action='store_true', default=False, 27 | help='Show mobile sessions.') 28 | parser.add_argument('-b', '--before', dest='date', 29 | help='List all device sessions connected before this date (yyyy-mm-dd format). ' 30 | 'All will be returned if unspecified.') 31 | 32 | args = parser.parse_args() 33 | 34 | # Get all types if none specified 35 | if not args.web and not args.desktop and not args.mobile: 36 | args.web = args.desktop = args.mobile = True 37 | 38 | before_date = None 39 | if args.date: 40 | before_date = datetime.datetime.strptime(args.date, "%Y-%m-%d") 41 | 42 | token = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 43 | 44 | 45 | # Look up a team member from a tag/value combination, where tags can be 'email', 'team_member_id', or 'external_id' 46 | def get_dfb_member(tag, value): 47 | request = urllib2.Request('https://api.dropbox.com/2/team/members/get_info', 48 | json.dumps({ 'members': [{'.tag': tag, tag: value}]})) 49 | request.add_header("Authorization", "Bearer "+token) 50 | request.add_header("Content-type", 'application/json') 51 | 52 | try: 53 | response = json.loads(urllib2.urlopen(request).read()) 54 | if 'id_not_found' in response[0]: 55 | parser.error("Member "+value+" is not on the team") 56 | return response[0] 57 | 58 | # Exit on error here. Probably user not found or bad OAuth token. Show response. 59 | except urllib2.HTTPError, error: 60 | parser.error(error.read()) 61 | 62 | 63 | # Get a member's sessions that match the input date & type arguments 64 | def get_member_sessions(email): 65 | member_id = get_dfb_member('email', email)['profile']['team_member_id'] 66 | data = { 67 | 'include_web_sessions': args.web, 68 | 'include_desktop_clients': args.desktop, 69 | 'include_mobile_clients': args.mobile, 70 | 'team_member_id': member_id 71 | } 72 | request = urllib2.Request('https://api.dropboxapi.com/2/team/devices/list_member_devices', json.dumps(data)) 73 | request.add_header("Authorization", "Bearer "+token) 74 | request.add_header("Content-type", 'application/json') 75 | 76 | try: 77 | response = json.loads(urllib2.urlopen(request).read()) 78 | return list_sessions(member_id, email, response, False) 79 | except urllib2.HTTPError, error: 80 | parser.error(error.read()) 81 | 82 | 83 | # Get a team's sessions that match the input date & type arguments 84 | def get_team_sessions(cursor): 85 | 86 | data = { 87 | 'include_web_sessions': args.web, 88 | 'include_desktop_clients': args.desktop, 89 | 'include_mobile_clients': args.mobile 90 | } 91 | 92 | if cursor is not None: 93 | data["cursor"] = cursor 94 | 95 | request = urllib2.Request('https://api.dropboxapi.com/2/team/devices/list_team_devices', json.dumps(data)) 96 | request.add_header("Authorization", "Bearer "+token) 97 | request.add_header("Content-type", 'application/json') 98 | 99 | try: 100 | response = json.loads(urllib2.urlopen(request).read()) 101 | 102 | returned_sessions = [] 103 | for d in response["devices"]: 104 | returned_sessions = returned_sessions + list_sessions(d['team_member_id'], None, d, True) 105 | if response["has_more"]: 106 | returned_sessions = returned_sessions + get_team_sessions(cursor=response["cursor"]) 107 | return returned_sessions 108 | except urllib2.HTTPError, error: 109 | parser.error(error.read()) 110 | 111 | 112 | # Output sessions matching the date/type arguments specified them, then return them 113 | def list_sessions(member_id, member_email, sessions, all_team): 114 | 115 | # Look up member email, if we don't have it 116 | 117 | returned_sessions = [] 118 | 119 | # Desktop sessions 120 | key = 'desktop_clients' if all_team else 'desktop_client_sessions' 121 | if key in sessions: 122 | for s in sessions[key]: 123 | if show_session(s): 124 | if 'created' not in s: 125 | s['created'] = '' 126 | if member_email is None: 127 | member_email = get_dfb_member('team_member_id',member_id)['profile']['email'] 128 | csv_writer.writerow(['Desktop', s['created'], member_email, s['platform'] + ' ' + s['host_name'], s['session_id']]) 129 | returned_sessions.append({'.tag': 'desktop_client', 'session_id': s['session_id'], 130 | 'team_member_id': member_id, 'delete_on_unlink': False}) 131 | 132 | # Mobile sessions 133 | key = 'mobile_clients' if all_team else 'mobile_client_sessions' 134 | if key in sessions: 135 | for s in sessions[key]: 136 | if show_session(s): 137 | if 'created' not in s: 138 | s['created'] = '' 139 | if member_email is None: 140 | member_email = get_dfb_member('team_member_id',member_id)['profile']['email'] 141 | csv_writer.writerow(['Mobile', s['created'], member_email, s['device_name'], s['session_id']]) 142 | returned_sessions.append({'.tag': 'mobile_client', 'session_id': s['session_id'], 143 | 'team_member_id': member_id }) 144 | 145 | # Web sessions 146 | key = 'web_sessions' if all_team else 'active_web_sessions' 147 | if key in sessions: 148 | for s in sessions[key]: 149 | if show_session(s): 150 | if 'created' not in s: 151 | s['created'] = '' 152 | if member_email is None: 153 | member_email = get_dfb_member('team_member_id',member_id)['profile']['email'] 154 | csv_writer.writerow(['Web', s['created'], member_email, s['os'] + ' - ' + s['browser'], s['session_id']]) 155 | returned_sessions.append({'.tag': 'web_session', 'session_id': s['session_id'], 156 | 'team_member_id': member_id}) 157 | 158 | return returned_sessions 159 | 160 | 161 | # Returns true if a session should be shown, per the args. Session type (desktop/web/mobile) is filtered in the API call 162 | def show_session(session): 163 | if before_date is None: 164 | return True 165 | else: 166 | return 'created' in session and datetime.datetime.strptime(session['created'][:10],'%Y-%m-%d').replace(tzinfo=None) < before_date 167 | 168 | 169 | # Revoke a list of sessions 170 | def deactivate_sessions(sessions): 171 | request = urllib2.Request('https://api.dropboxapi.com/2/team/devices/revoke_device_session_batch', 172 | json.dumps({'revoke_devices': sessions})) 173 | request.add_header("Authorization", "Bearer "+token) 174 | request.add_header("Content-type", 'application/json') 175 | try: 176 | json.loads(urllib2.urlopen(request).read()) 177 | print 'Deactivated ' + str(len(sessions)) + ' session(s).' 178 | csv_writer.writerow(['Platform', 'Owner', 'Session ID']) 179 | for s in sessions: 180 | csv_writer.writerow([s['.tag'], get_dfb_member('team_member_id',s['team_member_id'])['profile']['email'], s['session_id']]) 181 | except urllib2.HTTPError, error: 182 | parser.error(error.read()) 183 | 184 | csv_writer.writerow(['Platform', 'Created Date', 'Owner', 'Device', 'Session ID']) 185 | 186 | sessions = [] 187 | 188 | # List device sessions for specified users if specified 189 | if args.users is not None: 190 | for u in args.users: 191 | sessions = sessions + get_member_sessions(u) 192 | # Else the whole team 193 | else: 194 | sessions = get_team_sessions(None) 195 | 196 | if args.revoke: 197 | if raw_input("Deactivate sessions? Type 'YES' to confirm. ") == "YES": 198 | deactivate_sessions(sessions) 199 | else: 200 | print "Skipping deactivation" 201 | 202 | -------------------------------------------------------------------------------- /Integrations/ListTeamApps.py: -------------------------------------------------------------------------------- 1 | # Lists all of the apps that team members have linked 2 | 3 | import urllib2 4 | import json 5 | import argparse 6 | import sys 7 | import csv 8 | 9 | reload(sys) 10 | sys.setdefaultencoding('UTF8') 11 | 12 | # Collect user input 13 | parser = argparse.ArgumentParser(description='Lists all of the apps that team members have linked.') 14 | parser.add_argument('-u', '--user', dest='users', action='append', 15 | help='Target users (email address) to scan. All team members will be scanned if unspecified. ' 16 | 'You may pass multiple -u arguments.') 17 | 18 | args = parser.parse_args() 19 | 20 | token = raw_input('Enter your Dropbox Business API App token (Team Member File access permission): ') 21 | 22 | 23 | # Look up a team member from a tag/value combination, where tags can be 'email', 'team_member_id', or 'external_id' 24 | def get_team_member(tag, value): 25 | 26 | data = {'members': [{'.tag': tag, tag: value}]} 27 | 28 | request = urllib2.Request('https://api.dropbox.com/2/team/members/get_info', json.dumps(data)) 29 | request.add_header("Authorization", "Bearer " + token) 30 | request.add_header("Content-type", 'application/json') 31 | 32 | try: 33 | response = json.loads(urllib2.urlopen(request).read()) 34 | return response[0] if len(response) > 0 else None 35 | 36 | # Exit on error here. Probably user not found or bad OAuth token. Show Dropbox response. 37 | except urllib2.HTTPError, error: 38 | parser.error(error.read()) 39 | 40 | 41 | # Get all team members, paging through results if necessary 42 | def get_team(cursor): 43 | 44 | data = {} 45 | endpoint = '' 46 | 47 | if cursor is not None: 48 | data["cursor"] = cursor 49 | endpoint = '/continue' 50 | 51 | request = urllib2.Request('https://api.dropbox.com/2/team/members/list' + endpoint, json.dumps(data)) 52 | request.add_header("Authorization", "Bearer " + token) 53 | request.add_header("Content-type", 'application/json') 54 | try: 55 | response = json.loads(urllib2.urlopen(request).read()) 56 | members = response["members"] 57 | 58 | if response["has_more"]: 59 | members = members + get_team(cursor=response["cursor"]) 60 | 61 | return members 62 | 63 | # Exit on error here. Probably bad OAuth token. Show Dropbox response. 64 | except urllib2.HTTPError, error: 65 | parser.error(error.read()) 66 | 67 | 68 | # Get all linked apps for the specified member 69 | def get_member_linked_apps(email): 70 | 71 | data = {'team_member_id': get_team_member('email', email)['profile']['team_member_id']} 72 | 73 | request = urllib2.Request('https://api.dropboxapi.com/2/team/linked_apps/list_member_linked_apps', json.dumps(data)) 74 | request.add_header("Authorization", "Bearer " + token) 75 | request.add_header("Content-type", 'application/json') 76 | 77 | try: 78 | return json.loads(urllib2.urlopen(request).read())['linked_api_apps'] 79 | 80 | # Exit on error here. Probably user not found or bad OAuth token. Show Dropbox response. 81 | except urllib2.HTTPError, error: 82 | parser.error(error.read()) 83 | 84 | 85 | # Get all linked apps for each member of the team 86 | def get_team_linked_apps(cursor): 87 | 88 | data = {} 89 | 90 | if cursor is not None: 91 | data["cursor"] = cursor 92 | 93 | request = urllib2.Request('https://api.dropboxapi.com/2/team/linked_apps/list_team_linked_apps', json.dumps(data)) 94 | request.add_header("Authorization", "Bearer " + token) 95 | request.add_header("Content-type", 'application/json') 96 | 97 | try: 98 | response = json.loads(urllib2.urlopen(request).read()) 99 | apps = response['apps'] 100 | 101 | if response["has_more"]: 102 | apps = apps + get_team_linked_apps(cursor=response["cursor"]) 103 | 104 | return apps 105 | 106 | # Exit on error here. Probably user not found or bad OAuth token. Show Dropbox response. 107 | except urllib2.HTTPError, error: 108 | parser.error(error.read()) 109 | 110 | 111 | # takes in the list of apps and who's using them and then adds the current user's list of apps to that 112 | def log_apps(user_email, user_apps, team_apps): 113 | 114 | # for each app a user has linked 115 | for a in user_apps: 116 | 117 | # if we haven't seen this app before, add it to the list of team apps 118 | if a['app_id'] not in team_apps: 119 | 120 | publisher = a['publisher'] if 'publisher' in a else '' 121 | name = a['app_name'] if 'app_name' in a else '' 122 | 123 | team_apps[a['app_id']] = {'app_name': name, 'publisher': publisher, 'users': []} 124 | 125 | # add the user's email to the list of people who use this app 126 | team_apps[a['app_id']]['users'].append(user_email) 127 | 128 | 129 | csv_writer = csv.writer(sys.stdout) 130 | csv_writer.writerow(['App', 'Publisher', '# of Users', 'Users']) 131 | 132 | # apps, where each object is app_id: { id, name, users } 133 | team_apps = dict() 134 | 135 | # Log linked apps for specified users, else log apps for entire team 136 | if args.users is not None: 137 | 138 | # for all users, get their apps and add it to the list of apps that the specified user(s) are using 139 | for u in args.users: 140 | log_apps(u, get_member_linked_apps(u), team_apps) 141 | 142 | else: 143 | 144 | # get list of all team members and convert to dict of k=member_id, v=email 145 | team_emails = dict() 146 | 147 | for t in get_team(None): 148 | team_emails[t['profile']['team_member_id']] = t['profile']['email'] 149 | 150 | # for each of the members of the team, if they've linked apps, log these to the list of team apps 151 | for a in get_team_linked_apps(None): 152 | if len(a['linked_api_apps']) > 0: 153 | log_apps(team_emails[a['team_member_id']], a['linked_api_apps'], team_apps) 154 | 155 | app_names = dict() 156 | 157 | # get the list of apps that we've run across and create a list of ids and number of users. *-1 makes it sort descending 158 | for k in team_apps: 159 | app_names[k] = -1*len(team_apps[k]['users']) 160 | 161 | # for each app in the list sorted by number of users, print it 162 | for key in sorted(app_names, key=app_names.get): 163 | app = team_apps[key] 164 | csv_writer.writerow([app['app_name'], app['publisher'], len(app['users']), ', '.join(app['users'])]) 165 | -------------------------------------------------------------------------------- /Integrations/listMembersLinkedApps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- ccoding: utf-8 -*- 3 | 4 | import json 5 | import requests 6 | import pprint # Allows Pretty Print of JSON 7 | import os # Allows for the clearing of the Terminal Window 8 | import csv # Allows outputting to CSV file 9 | import time, datetime 10 | import sys 11 | import logging 12 | 13 | """ 14 | Script to list all applications linked to the team members' accounts. 15 | Note, this script/endpoint does not list any team-linked applications. 16 | 17 | Scripts loads all Team Members to allow reporting on email address and linked apps. 18 | Script loads all apps and prints to a file at the same location as execution of script. 19 | 20 | It will generate one file: 21 | - linked_member_apps.csv 22 | 23 | 24 | Requirements: 25 | Script tested on Python 3.6.5 26 | 27 | One Dropbox API Token is needed, inserted just below this comments section. 28 | Permissions needed on token: 29 | - account_info.read "View basic information about your Dropbox account such as your username, email, and country" 30 | - team_data.member "View structure of your team's and members' folders" 31 | - members.read "View your team membership" 32 | - sessions.list "View your team's sessions, devices, and apps" 33 | 34 | 35 | Pre-requisites: 36 | * Scripts requires library 'Requests' - You can install using "pip install requests" 37 | 38 | """ 39 | 40 | 41 | 42 | 43 | 44 | """ 45 | Set your OAuth Tokens here 46 | """ 47 | 48 | gToken = '' # Scoped API Token 49 | 50 | 51 | 52 | 53 | """########################################################################################## 54 | DO NOT EDIT BELOW THIS POINT 55 | ##########################################################################################""" 56 | 57 | gTotalTimeStart = datetime.datetime.fromtimestamp(time.time()) 58 | 59 | 60 | ############################################# 61 | # Function to print Message to console in a tidy box 62 | ############################################# 63 | def printmessageblock( str ): 64 | print ("\n*********************************************************") 65 | print ("* %s" % (str)) 66 | print ("*********************************************************\n") 67 | return; 68 | 69 | ############################################# 70 | # Function to return current Timestamp 71 | ############################################# 72 | def getTimeYMDHM(): 73 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M') 74 | return lRightNow; 75 | 76 | ############################################# 77 | # Function to return Message to console in a tidy box 78 | ############################################# 79 | def getTimeInHoursMinutesSeconds( sec ): 80 | return time.strftime("%H hrs %M mins %S sec", time.gmtime(sec)) 81 | 82 | 83 | 84 | """ 85 | ############################################# 86 | # Step 1 87 | # 1. Setup the necessary variables to get list of members. 88 | ############################################# 89 | """ 90 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % gToken} 91 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 92 | aData = json.dumps({'limit': 300}) 93 | 94 | 95 | 96 | 97 | """ 98 | ############################################# 99 | # Step 2 100 | # 1. Get list of all Dropbox Team Members 101 | # 2. Create in memory list of them. 102 | ############################################# 103 | """ 104 | hasMore = True; # Controls how long we stay in while loop loading users. 105 | loopCounter = 0 # Count of how many times we hit the API 106 | dbxUsers = [] # List of Dropbox Users 107 | dbxEmailLookup = {} # A quick reference list of key-pair values of team-member-ids and email addressses 108 | 109 | print ("> Retrieving Dropbox Users via API") 110 | timestart = datetime.datetime.fromtimestamp(time.time()) # Used to note start and calculare total time script took to run. 111 | 112 | while hasMore: 113 | 114 | print (">>> API call") 115 | """ Make the API call """ 116 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 117 | 118 | print ("<<< Results") 119 | 120 | # If we don't get a 200 HTML response code, we didn't get a result. 121 | if( aResult.status_code != 200 ): 122 | print ('>>> Failed to get a response to call for /team/members/list') 123 | logging.info( aResult.text ) 124 | print (aResult.text) 125 | exit(); 126 | 127 | # Note the JSON response 128 | members = aResult.json() 129 | 130 | # Iterate over the Members in the JSON 131 | for aMember in members['members']: 132 | dbxUsers.append( aMember ) 133 | dbxEmailLookup[ aMember['profile']['team_member_id'] ] = aMember['profile']['email'] 134 | 135 | hasMore = members['has_more'] # Note if there's another cursor call to make. 136 | 137 | # If it's the first run, from this point onwards the API call is the /continue version. 138 | if ( loopCounter >= 0 ): 139 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 140 | aData = json.dumps({'cursor': members['cursor']}) 141 | loopCounter += 1 142 | 143 | # How long did the APIs take? 144 | timestop = datetime.datetime.fromtimestamp(time.time()) 145 | strMessage = "We have the Dropbox users in memory from " + str(loopCounter) + " API Calls. it took " + str((timestop-timestart).total_seconds()) + " seconds." 146 | print ( strMessage ) 147 | logging.info( strMessage ) 148 | 149 | 150 | 151 | """ 152 | ############################################# 153 | # Step 3 154 | # 1. Get all Linked Apps 155 | ############################################# 156 | """ 157 | 158 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % gToken} 159 | aURL = 'https://api.dropboxapi.com/2/team/linked_apps/list_members_linked_apps' 160 | aData = json.dumps(None) 161 | 162 | hasMore = True; # Controls how long we stay in while loop 163 | loopCounter = 0 # Count of how many times we hit the API 164 | 165 | 166 | print ("> Retrieving Dropbox Users via API") 167 | timestart = datetime.datetime.fromtimestamp(time.time()) # Used to note start and calculare total time script took to run. 168 | 169 | 170 | 171 | fileName = ("%s_" + "linked_member_apps.csv") % getTimeYMDHM() 172 | 173 | 174 | with open( fileName, 'w') as csvfile: 175 | writer = csv.writer(csvfile, delimiter=',') 176 | # Write the Column Headers 177 | writer.writerow(['Email address', 'App ID', 'App Name', 'Linked Date']) 178 | 179 | 180 | while hasMore: 181 | 182 | print (">>> API call") 183 | """ Make the API call """ 184 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 185 | 186 | print ("<<< Results") 187 | 188 | # If we don't get a 200 HTML response code, we didn't get a result. 189 | if( aResult.status_code != 200 ): 190 | print ('>>> Failed to get a response to call for /2/team/linked_apps/list_members_linked_apps') 191 | logging.info( aResult.text ) 192 | print (aResult.text) 193 | exit(); 194 | 195 | # Note the JSON response 196 | memberLinkedApps = aResult.json() 197 | 198 | # Iterate over the Members in the JSON 199 | for aLinkedApp in memberLinkedApps['apps']: 200 | userEmailAddress = dbxEmailLookup.get( aLinkedApp['team_member_id'] ) # Get team members email address 201 | print ( "processing: " + userEmailAddress ) 202 | logging.info( "processing: " + userEmailAddress ) 203 | 204 | 205 | linked_api_apps = aLinkedApp['linked_api_apps'] 206 | 207 | for app in linked_api_apps: 208 | writer.writerow([userEmailAddress, app['app_id'], app['app_name'], app['linked']]) 209 | 210 | hasMore = memberLinkedApps['has_more'] # Note if there's another cursor call to make. 211 | 212 | # If it's the first run, from this point onwards the API call is the /continue version. 213 | if ( loopCounter >= 0 ): 214 | if ( hasMore ): 215 | aData = json.dumps({'cursor': memberLinkedApps['cursor']}) 216 | loopCounter += 1 217 | 218 | # How long did the APIs take? 219 | timestop = datetime.datetime.fromtimestamp(time.time()) 220 | strMessage = "We have the Dropbox users in memory from " + str(loopCounter) + " API Calls. it took " + str((timestop-timestart).total_seconds()) + " seconds." 221 | print ( strMessage ) 222 | logging.info( strMessage ) 223 | 224 | 225 | 226 | 227 | 228 | """ 229 | ############################################# 230 | # Step 2 231 | # 1. Output how long the script took to run. 232 | ############################################# 233 | """ 234 | gTotalTimeStop = datetime.datetime.fromtimestamp(time.time()) 235 | gTotalTimeInSeconds = (gTotalTimeStop-gTotalTimeStart).total_seconds() 236 | timeAsStr = getTimeInHoursMinutesSeconds( gTotalTimeInSeconds ) 237 | printmessageblock( " Script finished running, it took %s seconds." % ( timeAsStr ) ) 238 | -------------------------------------------------------------------------------- /Paper/convertWordToPaper.py: -------------------------------------------------------------------------------- 1 | #!#!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import requests 6 | import os # Allows for the clearing of the Terminal Window 7 | import csv # Allows outputting to CSV file 8 | import time, datetime 9 | import mammoth # Used to convert Word to Markdown/HTML 10 | import glob 11 | 12 | """ 13 | ******************************************************************************************************************** 14 | 15 | The intention of this script is to: 16 | * Iterate over a list of users email addresses and for every user look for a folder matching that email address in the 17 | same folder as this running script. 18 | For each .docx file in the folder, convert to HTML and create a Paper document matching that in the users Dropbox Account. 19 | 20 | If email address in the file 'users.csv' isn't a valid Team Members email address we skip it. 21 | 22 | Script expects: 23 | * a file of users called users.csv, in same folder as this script, which is a CSV list of email addresses, one email *lowercase* per row. 24 | * a folder called 'Paper Docs', in same folder as this script 25 | * for every user listed in CSV that there will be a folder name matching that email address ( again lowercase ) inside 26 | the folder 'WordDocs'. 27 | If it can't find a folder it moves onto next user in list. 28 | * the files per user to be .docx Word Documents 29 | 30 | 31 | Prerequisites: 32 | * Python 3.6+ 33 | * Requests library installed 'pip install requests' 34 | * Mammoth library installed 'pip install mammoth' [https://github.com/mwilliamson/python-mammoth] 35 | 36 | 37 | ******************************************************************************************************************** 38 | """ 39 | 40 | gTokenTMM = '' # Team Member Management token for TARGET team 41 | gTokenTMFA = '' # Team Member File Access 42 | 43 | gUsersList = 'users.csv' 44 | 45 | 46 | 47 | """ 48 | ******************************************************************************************************************** 49 | DO NOT EDIT BELOW THIS POINT 50 | ******************************************************************************************************************** 51 | """ 52 | 53 | ############################################# 54 | # Function to return a string representation of time taken 55 | ############################################# 56 | def getTimeInHoursMinutesSeconds( sec ): 57 | 58 | return time.strftime("%H hrs %M mins %S sec", time.gmtime(sec)) 59 | 60 | ############################################# 61 | # Function to print Message to console in a tidy box 62 | ############################################# 63 | def printmessageblock( str ): 64 | print ("\n*********************************************************") 65 | print ("* %s" % (str)) 66 | print ("*********************************************************\n") 67 | return; 68 | 69 | ############################################# 70 | # Function to return create a Paper document in a user Account 71 | ############################################# 72 | def uploadPaperDoc( team_member_id, tmfa_token, markupData ): 73 | 74 | # Get current directory 75 | #cwd = os.getcwd() 76 | #bytes_read = open(markupFile, "rb").read() 77 | 78 | 79 | lArguments = json.dumps({'import_format': 'html'}) 80 | 81 | lHeadersTMFA = {'Content-Type': 'application/octet-stream', 82 | 'Authorization': 'Bearer %s' % tmfa_token, 83 | 'Dropbox-API-Select-User': '%s' % team_member_id, 84 | 'Dropbox-API-Arg': '%s' % lArguments} 85 | 86 | 87 | lURL = "https://api.dropboxapi.com/2/paper/docs/create" 88 | 89 | aResult = requests.post(lURL, headers=lHeadersTMFA, data = markupData) # Create Paper Doc 90 | 91 | if( aResult.status_code != 200 ): 92 | printmessageblock ('* Failed to get a response to call for /paper/docs/create. \nWe got an error [%s] with text "%s"' % (aResult.status_code, aResult.text)) 93 | 94 | return; 95 | 96 | 97 | 98 | 99 | 100 | # Track how long script takes to run 101 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 102 | 103 | # Global Variables 104 | gUsers = [] 105 | 106 | ############################################# 107 | # Step 0 108 | # Clear the terminal window, not essential but makes it easier to read this way. 109 | ############################################# 110 | 111 | os.system('cls' if os.name=='nt' else 'clear') 112 | 113 | 114 | 115 | 116 | ############################################# 117 | # Step 1 118 | # Check that there's Tokens provided. 119 | ############################################# 120 | if ( len(gTokenTMM) <= 0 or len(gTokenTMFA) <= 0 ): 121 | printmessageblock ( "It would appear you're missing one of the necessary API Tokens. Ending script." ) 122 | exit() 123 | 124 | 125 | 126 | 127 | ############################################# 128 | # Step 2 129 | # Get the list of users to analyze 130 | ############################################# 131 | 132 | # Check we have a users file 133 | bHaveCSV = os.path.isfile( gUsersList ) 134 | 135 | if (not bHaveCSV): 136 | print('We could not find a file listing users to insert Paper Documents for. ') 137 | exit(); 138 | 139 | 140 | # Open file of users to upload files for 141 | with open( gUsersList, 'rt') as csvfileRead: 142 | # Open file to read from 143 | reader = csv.reader(csvfileRead) 144 | 145 | #Iterate through each row of the CSV. 146 | for row in reader: 147 | gUsers.append( row[0].lower() ) # Lower case so we can compare to Dropbox ( always lowercase ) 148 | 149 | if ( len(gUsers) <= 0 ): 150 | 151 | # Check that we have users 152 | print("We could not any users in file '%s' to work on. " % gUsersList) 153 | exit(); 154 | 155 | 156 | ############################################# 157 | # Step 3 158 | # Get a list of all Team Members 159 | # Only note the users we've been told to work on in inout users file 160 | ############################################# 161 | 162 | aHeaders = {'Content-Type': 'application/json', 163 | 'Authorization': 'Bearer %s' % gTokenTMM} 164 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 165 | aData = json.dumps({'limit': 300}) 166 | 167 | hasMore = True; # Controls how long we stay in while loop loading users. 168 | loopCounter = 0 # Count of how many times we hit the API 169 | dbxUsers = [] # List of Dropbox Users 170 | aTotalMembers = 0 171 | 172 | print ("> Retrieving Dropbox Users via API") 173 | timestart = datetime.datetime.fromtimestamp(time.time()) # Used to note start and calculate total time script took to run. 174 | 175 | while hasMore: 176 | 177 | print (">>> API call") 178 | """ Make the API call """ 179 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 180 | 181 | print ("<<< Results") 182 | 183 | # If we don't get a 200 HTML response code, we didn't get a result. 184 | if( aResult.status_code != 200 ): 185 | print ('>>> Failed to get a response to call for /team/members/list') 186 | print (aResult.text) 187 | exit(); 188 | 189 | # Note the JSON response 190 | members = aResult.json() 191 | 192 | # Iterate over the Members in the JSON 193 | for aMember in members['members']: 194 | 195 | aTotalMembers += 1. # Count number of team members 196 | 197 | #Check if this member is one we've been asked to work on. 198 | if (aMember['profile']['email'] in gUsers): 199 | dbxUsers.append( aMember ) 200 | 201 | hasMore = members['has_more'] # Note if there's another cursor call to make. 202 | 203 | # If it's the first run, from this point onwards the API call is the /continue version. 204 | if ( loopCounter >= 0 ): 205 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 206 | aData = json.dumps({'cursor': members['cursor']}) 207 | loopCounter += 1 208 | 209 | 210 | # How long did the APIs take? 211 | timestop = datetime.datetime.fromtimestamp(time.time()) 212 | print ((" We have %s Dropbox Team members in memory from %s API Calls. it took %s seconds.\n\n") % (str(len(dbxUsers)),str(loopCounter),str((timestop-timestart).total_seconds())) ) 213 | 214 | print( "\n\nThere are " + str(aTotalMembers) + " members on the team" ); 215 | print ( "We're to create Paper documents for " + str(len(dbxUsers)) + " users." ) 216 | 217 | 218 | 219 | 220 | 221 | ############################################# 222 | # Step 4 223 | # Iterate over each team member, look for a folder name matching that users 224 | # email address. 225 | # Iterate over every .docx file, convert to markdown and upload to users account! 226 | ############################################# 227 | 228 | for aCurrentUser in dbxUsers: 229 | 230 | # Locate a folder matching email address of this user 231 | # Check we have a folder 232 | thisPath = 'WordDocs/' + aCurrentUser['profile']['email'] 233 | bHaveCSV = os.path.isdir( thisPath ) 234 | 235 | if ( not bHaveCSV ): 236 | print ('\n--No source folder found for user ' + aCurrentUser['profile']['email']) 237 | continue; 238 | 239 | # Iterate over each file in Folder 240 | # First get list of .docx files 241 | docsToConvert = glob.glob( thisPath + '/*.docx' ) 242 | 243 | for aDoc in docsToConvert: 244 | print (aDoc) 245 | md = mammoth.convert_to_html( aDoc ) 246 | 247 | uploadPaperDoc( aCurrentUser['profile']['team_member_id'], gTokenTMFA, md.value ) 248 | 249 | 250 | 251 | 252 | 253 | ############################################# 254 | # Final step 255 | # 1. Output how long the script took to run. 256 | ############################################# 257 | 258 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 259 | totalTimeInSeconds = (totalTimeStop-totalTimeStart).total_seconds() 260 | timeAsStr = getTimeInHoursMinutesSeconds( totalTimeInSeconds ) 261 | printmessageblock( " Script finished running, it took %s." % ( timeAsStr ) ) 262 | 263 | 264 | print( "Script finished") 265 | -------------------------------------------------------------------------------- /Paper/paper-export.py: -------------------------------------------------------------------------------- 1 | # File: paper-export.py 2 | # Export Paper Docs Tool 3 | # Version 1.0 4 | # Author: Marcel Ribas - @macribas 5 | # Date: 3/31/2021 6 | 7 | # Python script to export Paper Docs from a Dropbox account. It can export in either HTML or Markdown. 8 | # It only works in accounts that have Paper In the FileSystem (PiFS). Script checks the account for that condition. 9 | # Does not work recursively, on purpose. You need to select the folders where your Paper docs are. Saves files in the working local folder. 10 | # Once you are comfortable with running this, then you can modify it to work recursively. 11 | # Your API key needs to have Full Dropbox access and files.content.read scope. 12 | 13 | import dropbox 14 | import os 15 | 16 | # Dropbox 17 | try: 18 | dbx = dropbox.Dropbox('') 19 | # check if account has PiFS 20 | features = dbx.users_features_get_values([dropbox.users.UserFeature.paper_as_files]) 21 | pifs = features.values[0].get_paper_as_files().get_enabled() 22 | except dropbox.exceptions.AuthError: 23 | print("It was not possible to connect to your Dropbox account. Please try another token.") 24 | print("You need the files.content.read scope") 25 | quit() 26 | 27 | if not pifs: 28 | print("This account does not have Paper In The FileSystem (PiFS) enabled") 29 | quit() 30 | 31 | while True: 32 | path = input("Enter the Dropbox path for your Paper docs ( for the root folder): ") 33 | if path.startswith('/') or not path: 34 | break; 35 | else: 36 | print("Invalid folder name, please try again.") 37 | 38 | while True: 39 | go_on = input("This process might take a while, depending on the size of the folder you are traversing. Continue (Y or N)? ") 40 | if go_on.upper() == 'Y': 41 | break; 42 | elif go_on.upper() == 'N': 43 | quit() 44 | 45 | print("Processing") 46 | 47 | # Check if folder exists 48 | try: 49 | folder = dbx.files_list_folder(path) 50 | cursor = folder.cursor 51 | except dropbox.exceptions.DropboxException: 52 | print("Could not find folder {0}".format(path)) 53 | quit() 54 | 55 | # if file is paper doc, put it in list 56 | paper_docs = [file.path_display for file in folder.entries if isinstance(file, dropbox.files.FileMetadata) if not file.is_downloadable if os.path.splitext(file.path_lower)[1] == ".paper"] 57 | 58 | while folder.has_more: 59 | print("Still working") 60 | folder = dbx.files_list_folder_continue(cursor) 61 | cursor = folder.cursor 62 | paper_docs += [file.path_display for file in folder.entries if isinstance(file, dropbox.files.FileMetadata) if not file.is_downloadable if os.path.splitext(file.path_lower)[1] == ".paper"] 63 | 64 | size = len(paper_docs) 65 | if size == 0: 66 | print("You don't have any Paper docs in this path. ") 67 | quit() 68 | else: 69 | if path: 70 | folder_name = path 71 | else: 72 | folder_name = "the root folder" 73 | print("You have {0} Paper docs in {1}.".format(size,folder_name)) 74 | 75 | while True: 76 | export = input("Do you want to export these to your computer? (Y/N) ") 77 | if export.upper() == 'Y': 78 | break; 79 | elif export.upper() == 'N': 80 | quit() 81 | 82 | print("These Paper docs will be exported to the folder where you are running this script from.") 83 | 84 | while True: 85 | format = input("Which format do you want to export as? (1) HTML or (2) Markdown? (3) to quit: ") 86 | if format == '1': 87 | export_as = ("html",".html") 88 | break 89 | elif format == '2': 90 | export_as = ("markdown",".md") 91 | break 92 | elif format == '3': 93 | quit() 94 | else: 95 | print("Invalid format") 96 | 97 | for paper_doc in paper_docs: 98 | folder, filename = os.path.split(paper_doc) 99 | basename, ext = os.path.splitext(filename) 100 | 101 | print("Exporting {0} as {1}".format(paper_doc, basename + export_as[1])) 102 | 103 | with open(basename + export_as[1], "wb") as f: 104 | metadata, res = dbx.files_export(path=paper_doc,export_format=export_as[0]) 105 | f.write(res.content) 106 | 107 | print("Export completed!") 108 | -------------------------------------------------------------------------------- /QA Installer/Client-Event-Codes.md: -------------------------------------------------------------------------------- 1 | # Client Event Codes 2 | 3 | | **Code** | **Type** | **Message** | **Meaning** | 4 | | -------- | ----------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 5 | | 0 | Information | Generic Informational Message | Used for non-specific application output | 6 | | 100 | Error | Dropbox installation version not specfied. Please contact the Helpdesk. | The version number to be installed hasn’t been set, this means that the Link, Forced Value and UNC path haven’t been written | 7 | | 99 | Error | Unable to create Dropbox event log, please contact the Helpdesk. | We’re not able to create an Event Log source, which means we can’t write errors and progress to the log. These are necessary so that administrators can fault find | 8 | | 1 | Error | You do not have Administrator rights to run this script! Please re-run this script as an Administrator! | Dropbox needs to install with Administrator level permissions to enable features like Smart Sync. | 9 | | 2 | Error | Unable to confirm version number. Please contact the Helpdesk. | The Dropbox link specified is corrupted or the file it references has the version number that is not on line 1. | 10 | | 3 | Error | Cannot connect to file share. Dropbox cannot install please contact the Helpdesk. | The path to the file over UNC doesn’t resolve to a file | 11 | | 4 | Error | Version number corrupted! Please contact the Helpdesk. | The file it references has the version number that is not on line 1, or the file contains a version number that doesn’t map to a.b.c | 12 | | 5 | Information | Dropbox not installed... | Dropbox isn’t installed on this machine, proceed to first install. | 13 | | 6 | Information | Downloading Dropbox | Dropbox is being downloaded | 14 | | 7 | Error | Cannot download Dropbox. Please contact the Helpdesk. | The download of Dropbox has failed, this could be a network or proxy issue. | 15 | | 8 | Information | Installing Dropbox... | Dropbox is now being installed | 16 | | 9 | Error | Cannot install Dropbox. Please contact the Helpdesk. | The Dropbox installer failed to run | 17 | | 10 | Error | Could not disable some or all of the Dropbox Update tasks, please contact the Helpdesk. | The Dropbox Updater scheduled tasks were not disabled | 18 | | 11 | Information | Dropbox version valid and Authenticode approved | The client has tested the Authenticode signature for the Dropbox installer and it is valid | 19 | | 12 | Error | Dropbox version failed Authenticode check. Installation failed. Please contact the Helpdesk | The client has tested the Authenticode signature for the Dropbox installer and it failed. The installer may be corrupted or tampered with. | 20 | | 13 | Error | Script not signed, aborting. | If the script isn’t signed then it shouldn’t be running. We take this seriously and won’t allow `–ExecutionPolicy Bypass` it’s not big and it’s not clever. | 21 | 22 | 23 | -------------------------------------------------------------------------------- /QA Installer/Dropbox Enterprise Installer.ps1: -------------------------------------------------------------------------------- 1 | #Dropbox Powershell Enterprise Installer 2 | #Version 0.4 3 | <# 4 | Download and install the latest version of Dropbox for the purposes of Enterprise deployment 5 | This script can be run as part of a user logon script 6 | 7 | Update the $approved_ver with the value of your choice, this can be centralised 8 | using either: 9 | * Preset value in this Script 10 | * Network Share (I appreciate the irony) 11 | * Dropbox shared link, this must be publically accessible 12 | 13 | Decide which one is appropriate for your environment and use as appropriate 14 | 15 | This script MUST be signed to run and MUST be run as an admin or SYSTEM 16 | 17 | #> 18 | 19 | $source = $env:TEMP #Change this to a download directory of your choosing 20 | $arguments = '/s' #Change /s to /NOLAUNCH to prevent client start 21 | $testing = 1 #Change to 0 to install/update 22 | 23 | #Choose how you will find the correct version number for Dropbox to install 24 | $approved_ver = '' #'26.1.9' 25 | $approved_path = '' #'' #'\\NETWORKFILESYSTEM\Directory\approved.txt' 26 | $approved_link = "" #Use a Dropbox shared link (must be publically accessible) 27 | 28 | ###### NO EDITTING BELOW THIS LINE ####### 29 | #Handle errors by displaying a message and logging 30 | Function ErrorHandler{ 31 | Param($Message, $Code, $Type) 32 | Write-host $Message 33 | Write-EventLog -LogName "Application" -Source "Dropbox PowerShell" -EventID $Code -EntryType $Type -Message $Message 34 | if($Type -eq "Error"){exit} #Die if fatal 35 | } 36 | 37 | #Check to see if at least one version 38 | if(!$approved_ver -and !$approved_path -and !$approved_link){ 39 | ErrorHandler -Message 'Dropbox installation version not specfied. Please contact the Helpdesk.' -Code 100 -Type 'Error' 40 | } 41 | 42 | #Create or update a new Event Log 43 | If ([System.Diagnostics.EventLog]::SourceExists("Dropbox PowerShell")){ 44 | }else{ 45 | try{ 46 | New-EventLog –LogName "Application" –Source "Dropbox PowerShell" 47 | }catch{ 48 | ErrorHandler -Message 'Unable to create Dropbox event log, please contact the Helpdesk.' -Code 99 -Type 'Error' 49 | } 50 | } 51 | 52 | #Check to see if the user has the Administrator role on the machine, if not die 53 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` 54 | [Security.Principal.WindowsBuiltInRole] "Administrator")){ 55 | ErrorHandler -Message "You do not have Administrator rights to run this script! Please re-run this script as an Administrator!" -Code 1 -Type 'Error' 56 | } 57 | 58 | #Grab the file if using Dropbox 59 | If ($approved_link) { 60 | #Check to see if link is live 61 | $approved_link = $approved_link -replace "dl=0", "dl=1" 62 | $wc = new-object system.net.WebClient 63 | $webpage = $wc.DownloadData($approved_link) 64 | $approved_ver = [System.Text.Encoding]::ASCII.GetString($webpage) 65 | #Check that the first line of the response has a version number 66 | if([regex]::match($approved_ver,'^\d{1,3}\.\d{1,3}\.\d{1,3}').success -eq 0){ 67 | ErrorHandler -Message 'Unable to confirm version number. Please contact the Helpdesk.' -Code 2 -Type 'Error' 68 | } 69 | } 70 | 71 | #Grab the file if using UNC Path 72 | If ($approved_path) { 73 | try{ 74 | $approved_ver = Get-Content -Path $approved_path 75 | }catch{ 76 | ErrorHandler -Message 'Cannot connect to file share. Dropbox cannot install please contact the Helpdesk.' -Code 3 -Type 'Error' 77 | } 78 | } 79 | 80 | #Validate version number 81 | If (-NOT ($approved_ver -match '^\d{1,3}\.\d{1,3}\.\d{1,3}')){ 82 | ErrorHandler -Message 'Version number corrupted! Please contact the Helpdesk.' -Code 4 -Type 'Error' 83 | } 84 | 85 | #Validate your inputs! 86 | $approved_ver = [regex]::Match($approved_ver,'^\d{1,3}\.\d{1,3}\.\d{1,3}') 87 | 88 | #Check to see if Dropbox is installed 89 | try{ 90 | #Find the Dropbox client version number from the registry 91 | #$client_ver1 = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\DropboxUpdate\Update\Clients\{CC46080E-4C33-4981-859A-BBA2F780F31E}\' -ErrorAction Stop | Select-Object -ExpandProperty pv 92 | $client_ver2 = Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Dropbox\Client\' -ErrorAction Stop | Select-Object -ExpandProperty Version 93 | }catch{ 94 | $client_ver2 = 0 95 | ErrorHandler -Message 'Dropbox not installed...' -Code 5 -Type 'Information' 96 | } 97 | 98 | #Check version of Dropbox and install update if required 99 | If ($client_ver2 -eq 0 -or ($client_ver2 -lt $approved_ver)){ 100 | 101 | $url = "https://clientupdates.dropboxstatic.com/client/Dropbox%20" + $approved_ver + "%20Offline%20Installer.exe" 102 | $fileName = Split-Path $url -Leaf 103 | $webClient = New-Object System.Net.WebClient 104 | $destinationPath = $source + "\DropboxOfflineInstaller.exe" #Overwrite any pre-existing installer 105 | ErrorHandler -Message 'Downloading Dropbox' -Code 6 -Type 'Information' 106 | try{ 107 | if($testing -ne 1){ 108 | $webClient.DownloadFile($url,$destinationPath) 109 | $file = Get-AuthenticodeSignature $destinationPath 110 | if($file.Status -eq "Valid") { 111 | ErrorHandler -Message 'Dropbox version valid and Authenticode approved' -Code 11 -Type 'Information' 112 | }else{ 113 | ErrorHandler -Message 'Dropbox version failed Authenticode check. Installation failed. Please contact the Helpdesk' -Code 12 -Type 'Error' 114 | } 115 | } #Don't download if in testing mode 116 | }catch{ 117 | ErrorHandler -Message 'Cannot download Dropbox. Please contact the Helpdesk.' -Code 7 -Type 'Error' 118 | } 119 | try{ 120 | if($testing -ne 1){Invoke-Expression -Command "$destinationPath $arguments"} #Don't install if in testing mode 121 | ErrorHandler -Message 'Installing Dropbox...' -Code 8 -Type 'Information' 122 | }catch{ 123 | ErrorHandler 'Cannot install Dropbox. Please contact the Helpdesk.' -Code 9 -Type 'Error' 124 | } 125 | } 126 | 127 | #Disable scheduled tasks, this should also be enforced by GPO, this is needed to prevent client updating 128 | #Stop any running Dropbox tasks 129 | try{ 130 | Stop-ScheduledTask -TaskName "DropboxUpdateTaskMachineCore" -ErrorAction SilentlyContinue | Out-Null 131 | Disable-ScheduledTask -TaskName "DropboxUpdateTaskMachineCore" -ErrorAction SilentlyContinue | Out-Null 132 | Stop-ScheduledTask -TaskName "DropboxUpdateTaskMachineUA" -ErrorAction SilentlyContinue | Out-Null 133 | Disable-ScheduledTask -TaskName "DropboxUpdateTaskMachineUA" -ErrorAction SilentlyContinue | Out-Null 134 | }catch{ 135 | ErrorHandler -Message 'Could not disable some or all of the Dropbox Update tasks, please contact the Helpdesk.' -Code 10 -Type 'Error' 136 | } 137 | 138 | <# 139 | Copyright (c) 2017 John Bradshaw 140 | 141 | Licensed under the Apache License, Version 2.0 (the "License"); 142 | you may not use this file except in compliance with the License. 143 | You may obtain a copy of the License at 144 | 145 | http://www.apache.org/licenses/LICENSE-2.0 146 | 147 | Unless required by applicable law or agreed to in writing, software 148 | distributed under the License is distributed on an "AS IS" BASIS, 149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 150 | See the License for the specific language governing permissions and 151 | limitations under the License.#> -------------------------------------------------------------------------------- /QA Installer/README.md: -------------------------------------------------------------------------------- 1 | # Enterprise Installer 2 | 3 | A set of scripts designed to allow for gated distribution of Dropbox desktop client updates. 4 | 5 | # Methodology 6 | The Enterprise Installer works via two scripts: 7 | - Dropbox Enterprise Installer QA 8 | Intended to run on QA systems. Acts as the gating mechanism to determine when a release is suitable for deployment to the production environment. 9 | - Dropbox Enterprise Installer 10 | Run as a login script, this will perform the installation of a Dropbox desktop client that has been released from the QA environment. It will also disable auto-updating post-installation to ensure that clients only update via this process. 11 | 12 | ![High level Architecture](https://www.dropbox.com/scl/fi/mru2cvr3lnnyenh3f5kgf/DbxEI_arch_high_level.JPG?raw=1 "High Level Architecture") 13 | 14 | The Enterprise Installer scripts assume two environments will be used 15 | - QA 16 | - This environment will have one or more devices configured to accept Dropbox updates directly and automatically (standard mode of client operation) 17 | - The Enterprise Installer QA script will be scheduled to run at least daily on a device within the environment 18 | - Production 19 | - This environment will use the Enterprise Installer installation script to deploy QA approved Dropbox client installations at the point of login 20 | - The Enterprise Installer installation script must be configured to run at login on all devices 21 | 22 | The process of getting a release out to production works as follows: 23 | 1. QA device(s) update from Dropbox directly 24 | 2. Enterprise Installer QA script is run, each new version seen goes into a version history which is then checked against either an N-x or minimum age gating mechanism. 25 | 3. When a release passes the gating mechanism, it is downloaded, verified, and moved to some form of intermediate storage (typically a UNC path) and a release version file is produced containing the currently approved version. 26 | 4. On the next run of the Enterprise Installer installation script (at login) it will check for a new release version, retrieve it from the intermediate storage, and then perform a silent installation on the endpoint device. When this installation is complete the tasks that would normally auto-update the endploint device will be disabled to prevent updates from happening out-of-band from the Enterprise Installer process. 27 | --- 28 | # Usage 29 | The Enterprise Installer is open source software and is unsupported by Dropbox. 30 | *__Use at your own risk, YMMV, caveat emptor, here be dragons, and all other pertinent warnings apply.__* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DropboxBusinessScripts 2 | #### Dropbox Business & Dropbox Enterprise Scripts 3 | 4 | Included here are scripting resources to serve as a base for common Dropbox Business and Dropbox Enterprise tasks. 5 | 6 | ### Licensing 7 | 8 | All scripts within this folder are covered by the Apache License as described in LICENSE.txt. 9 | 10 | ##### Please carefully note: 11 | 12 | > "Disclaimer of Warranty. [...] the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License." 13 | 14 | ### Script Conventions 15 | 16 | Every script should: 17 | 18 | - Have comments at top of file regarding usage (including API permission) 19 | - Take command-line arguments. (script) -h should print usage/description 20 | - Use argparse for python 21 | - Use commons-cli for java 22 | - Javascript/php/powershell equivalents? 23 | — Prompt for API token (and tell you type/permission level it needs) 24 | - We *don’t* want to save the token in the file, or pass it as an arg on CLI (too easy to accidentally expose in file/bash_history) 25 | - Use camel-cased file names (no dashes). 3-5 words. Roughly equivalent scripts in the same language should share the same name. 26 | 27 | ### Tips 28 | 29 | - For help in powershell: `Get-Help .\filename.ps1` 30 | - Internationalization: Test scripts with some non-latin characters in file strings / usernames when possible. For example, python needs to call reload(sys) / sys.setdefaultencoding('UTF8') to be happy with nonlatin strings. -------------------------------------------------------------------------------- /Sharing/ExternallySharedUsers.py: -------------------------------------------------------------------------------- 1 | # Lists external shared users 2 | 3 | import urllib2 4 | import json 5 | import argparse 6 | import sys 7 | import re 8 | import threading 9 | 10 | reload(sys) 11 | sys.setdefaultencoding('UTF8') 12 | 13 | # Collect user input 14 | parser = argparse.ArgumentParser(description='Identifies externally shared users') 15 | parser.add_argument('-d', '--domain', dest='domains', action='append', required=False, 16 | help='Target domains (i.e. acme.com in email@acme.com) to scan. If not indicated, will list all ' 17 | 'external users. You may pass multiple -d arguments.') 18 | args = parser.parse_args() 19 | token = raw_input('Enter your Dropbox Business API App token (Team Member File access permission): ') 20 | 21 | 22 | # Get all team members, paging through results if necessary 23 | def get_team_members(cursor): 24 | 25 | data = {"limit": 100} 26 | 27 | if cursor is not None: 28 | data["cursor"] = cursor 29 | 30 | request = urllib2.Request('https://api.dropbox.com/1/team/members/list', json.dumps(data)) 31 | request.add_header("Authorization", "Bearer " + token) 32 | request.add_header("Content-type", 'application/json') 33 | try: 34 | response = json.loads(urllib2.urlopen(request).read()) 35 | members = response["members"] 36 | 37 | if response["has_more"]: 38 | members = members + get_team_members(cursor=response["cursor"]) 39 | 40 | return members 41 | 42 | # Exit on error here. Probably bad OAuth token. Show Dropbox response. 43 | except urllib2.HTTPError, error: 44 | parser.error(error.read()) 45 | 46 | 47 | # get membership of all shared folders. may be limited to certain domains 48 | def get_shared_users(team_member_id, users, folders, domains): 49 | 50 | request = urllib2.Request('https://api.dropbox.com/1/shared_folders?include_membership=true&show_unmounted=true') 51 | request.add_header("Authorization", "Bearer " + token) 52 | request.add_header("X-Dropbox-Perform-As-Team-Member", team_member_id) 53 | 54 | try: 55 | 56 | # for each shared folder, 57 | for folder in json.loads(urllib2. urlopen(request).read()): 58 | 59 | # if we haven't logged that shared folder before 60 | if folder['shared_folder_id'] not in folders: 61 | 62 | folders.append(folder['shared_folder_id']) 63 | 64 | # for each member of the shared folder 65 | for folder_member in folder['membership']: 66 | 67 | user_email = folder_member['user']['email'] 68 | domain = str(re.search("@[\w.]+", user_email).group()[1:]).lower() 69 | 70 | # if they're an external member we haven't seen before and this user's domain is one we're checking 71 | if folder_member['user']['same_team'] is False and user_email not in users and \ 72 | (len(domains) == 0 or domain in domains): 73 | 74 | # add their email and domain 75 | users[user_email] = domain 76 | 77 | # Exit on error here. Probably bad OAuth token. Show DfB response. 78 | except urllib2.HTTPError, error: 79 | parser.error(error.read()) 80 | 81 | 82 | # helper function for threading 83 | def worker(team_subset, users, folders, domains): 84 | 85 | # get all unique shared folder external members 86 | for m in team_subset: 87 | if m['profile']['status'] == 'active': 88 | get_shared_users(m['profile']['member_id'], users, folders, domains) 89 | 90 | print 'Getting all team members...' 91 | 92 | # all members of a Dropbox team 93 | team_members = get_team_members(None) 94 | 95 | # keep track of shared folders and users we've already checked 96 | shared_folders = [] 97 | shared_users = dict() 98 | domains = [] 99 | 100 | if args.domains: 101 | for d in args.domains: 102 | domains.append(str(d).lower()) 103 | 104 | print 'Finding externally shared users at ' + ('all domains' if len(domains) == 0 else str(', '.join(domains))) + '...' 105 | 106 | # batches of team members to distribute in about 100 threads 107 | batch_size = 1 if len(team_members) < 100 else len(team_members) / 100 108 | threads = [] 109 | batches = 0 110 | 111 | while len(team_members) > 0: 112 | 113 | # split the team_members into batches to run concurrently 114 | subset = team_members[:batch_size] if len(team_members) >= batch_size else team_members 115 | team_members = team_members[len(subset):] 116 | 117 | # run thread to find external shares for that subset of the team members 118 | t = threading.Thread(target=worker, args=(subset, shared_users, shared_folders, domains)) 119 | threads.append(t) 120 | t.start() 121 | 122 | # prevent main thread from continuing until all of the threads have finished 123 | for t in threads: 124 | t.join() 125 | 126 | # sort by domain, then by user 127 | for u in sorted(shared_users.items(), key=lambda x: (x[1], x[0])): 128 | print u[0] 129 | -------------------------------------------------------------------------------- /Sharing/ListSharedFolders-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | kanderson 4 | ListSharedFolders 5 | 0.0.1 6 | 7 | 8 | 9 | org.apache.httpcomponents 10 | httpclient 11 | 4.5 12 | 13 | 14 | 15 | org.json 16 | json 17 | 20141113 18 | 19 | 20 | 21 | commons-cli 22 | commons-cli 23 | 1.3 24 | 25 | 26 | 27 | com.opencsv 28 | opencsv 29 | 3.4 30 | 31 | 32 | 33 | 34 | 35 | 36 | . 37 | 38 | 39 | maven-assembly-plugin 40 | 41 | 42 | 43 | ListSharedFolders 44 | 45 | 46 | 47 | jar-with-dependencies 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Sharing/ListSharedLinks.py: -------------------------------------------------------------------------------- 1 | # Lists all active shared links 2 | 3 | import urllib2 4 | import json 5 | import argparse 6 | import sys 7 | import csv 8 | 9 | reload(sys) 10 | sys.setdefaultencoding('UTF8') 11 | 12 | parser = argparse.ArgumentParser(description='Lists all files by user in the DfB team.') 13 | parser.add_argument('-u', '--user', dest='users', action='append', 14 | help='Target user (email address) to scan. All team members will be returned if unspecified. ' 15 | 'You may pass multiple -u arguments.') 16 | parser.add_argument( '-p', '--public', action='store_const', const=True, default=False, dest='public', 17 | help='Show unprotected public links only - won\'t list team only or password protected links' ) 18 | 19 | args = parser.parse_args() 20 | 21 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 22 | 23 | 24 | #Look up a DfB member from an email address 25 | def getDfbMember(emails): 26 | 27 | members = [] 28 | 29 | for e in emails: 30 | members.append({".tag": "email", "email": e}) 31 | 32 | data = {"members": members} 33 | 34 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/get_info', json.dumps(data)) 35 | request.add_header("Authorization", "Bearer "+dfbToken) 36 | request.add_header("Content-type", 'application/json') 37 | try: 38 | return json.loads(urllib2.urlopen(request).read()) 39 | 40 | # Exit on error here. Probably user not found or bad OAuth token. Show DfB response. 41 | except urllib2.HTTPError, error: 42 | parser.error(error.read()) 43 | 44 | 45 | # Get all DfB members, paging through results if necessary 46 | def getDfbMembers(cursor): 47 | 48 | data = {} 49 | endpoint = '' 50 | 51 | if cursor is not None: 52 | data = { 53 | "cursor": cursor 54 | } 55 | endpoint = 'https://api.dropboxapi.com/2/team/members/list/continue' 56 | else: 57 | data = { 58 | "limit": 100, 59 | "include_removed": False 60 | } 61 | endpoint = 'https://api.dropboxapi.com/2/team/members/list' 62 | 63 | request = urllib2.Request(endpoint, json.dumps(data)) 64 | request.add_header("Authorization", "Bearer "+dfbToken) 65 | request.add_header("Content-type", 'application/json') 66 | try: 67 | response = json.loads(urllib2.urlopen(request).read()) 68 | members = response["members"] 69 | 70 | if response["has_more"]: 71 | members = members + getDfbMembers(cursor=response["cursor"]) 72 | 73 | return members 74 | 75 | # Exit on error here. Probably bad OAuth token. Show DfB response. 76 | except urllib2.HTTPError, error: 77 | parser.error(error.read()) 78 | 79 | 80 | # List a member's shared links 81 | def listSharedLinks(memberEmail, memberId, csvwriter): 82 | 83 | try: 84 | 85 | request = urllib2.Request('https://api.dropboxapi.com/2/sharing/get_shared_links', json.dumps({})) 86 | request.add_header("Authorization", "Bearer " + dfbToken) 87 | request.add_header("Content-Type", 'application/json') 88 | request.add_header("Dropbox-API-Select-User", memberId) 89 | 90 | response_string = urllib2.urlopen(request).read() 91 | response = json.loads(response_string) 92 | 93 | for link in response["links"]: 94 | if (args.public is False) or (args.public is True and link['visibility']['.tag'] == 'public'): 95 | csvwriter.writerow([memberEmail, link['path'] if 'path' in link else '', link['visibility']['.tag'], 96 | link['url']]) 97 | 98 | except urllib2.HTTPError as error: 99 | csvwriter.writerow([memberEmail, error, "ERROR", memberId]) 100 | sys.stderr.write(" ERROR: {}\n".format(error)) 101 | 102 | csvwriter = csv.writer(sys.stdout) 103 | csvwriter.writerow(['User', 'Path', 'Visibility', 'URL']) 104 | 105 | members = [] 106 | 107 | if args.users is not None: 108 | members = getDfbMember(args.users) 109 | else: 110 | members = getDfbMembers(None) 111 | 112 | for member in members: 113 | if member["profile"]["status"][".tag"] == "active": 114 | listSharedLinks(member["profile"]["email"], member["profile"]["team_member_id"], csvwriter) 115 | 116 | -------------------------------------------------------------------------------- /Sharing/dbx_email_alerts.py: -------------------------------------------------------------------------------- 1 | #install the dropbox SDK with 'pip install dropbox' 2 | import dropbox 3 | import datetime 4 | import time 5 | import smtplib 6 | import requests 7 | 8 | #requires Dropbox Business API token with 'Team Auditing' permission 9 | token = "" 10 | cursor = None 11 | 12 | # instantiating dropbox team object 13 | dbxt = dropbox.DropboxTeam(token) 14 | 15 | # Full list of alerts available at: 16 | # https://www.dropbox.com/developers/documentation/http/teams#team_log-get_events 17 | alerts = {"sign_in_as_session_start", 18 | "member_change_admin_role", 19 | "shared_link_create", 20 | # "login_fail", 21 | # "shared_folder_create", 22 | # "file_request_create", 23 | # "account_capture_relinquish_account", 24 | # "shared_content_copy" 25 | } 26 | 27 | # If using gmail, "enable less secure apps" needs to be turned on. 28 | # https://myaccount.google.com/security -> "Enable less secure apps" 29 | # For a more robust solution, use an email API tool e.g. Mailgun 30 | sender_email = "" 31 | sender_pw = " List of user and the paths to shared links. 18 | 19 | Pre-requisites: 20 | * Scripts requires library 'Requests' - You can install using "pip install requests" 21 | 22 | """ 23 | 24 | """ 25 | Set your OAuth Token here with 'Team Member Management' permissions 26 | """ 27 | aTokenTMM = '' # Team Member Management 28 | aTokenTMFA = '' # Team Member File Access 29 | 30 | 31 | 32 | 33 | """ 34 | DO NOT EDIT BELOW THIS POINT 35 | """ 36 | 37 | fileName = 'sharedLinks.csv' 38 | 39 | 40 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 41 | 42 | 43 | ############################################# 44 | # Function to return current Timestamp 45 | ############################################# 46 | def getTimeYMDHM(): 47 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M') 48 | return lRightNow; 49 | 50 | 51 | ############################################# 52 | # Function to print Message to console in a tidy box 53 | ############################################# 54 | def printmessageblock( str ): 55 | print ("\n*********************************************************") 56 | print ("* %s" % (str)) 57 | print ("*********************************************************\n") 58 | return; 59 | 60 | ############################################# 61 | # Function to return a Token which used a 62 | # Team Member File Access token & Member ID 63 | ############################################# 64 | def getTokenWithTeamMemberFileAccess( aTokenTMFA, member_id ): 65 | lHeadersTMFA = {'Content-Type': 'application/json', 66 | 'Authorization': 'Bearer %s' % aTokenTMFA, 67 | 'Dropbox-API-Select-User': '%s' % str(member_id)} 68 | return lHeadersTMFA; 69 | 70 | 71 | ############################################# 72 | # Function to get list of Team Members paths 73 | ############################################# 74 | def getTeamMemberSharedLinks( aMember ): 75 | aURL = 'https://api.dropboxapi.com/2/sharing/list_shared_links' 76 | aData = json.dumps({}) 77 | 78 | 79 | hasMore = True; 80 | loopCounter = 0 81 | 82 | timestart = datetime.datetime.fromtimestamp(time.time()) 83 | 84 | lHeaders = getTokenWithTeamMemberFileAccess( aTokenTMFA, aMember[1] ) 85 | 86 | # List of member Shared Links 87 | memberSharedLinks = [] 88 | 89 | while hasMore: 90 | print ("\n+ %s" % aMember[0]) 91 | aResult = requests.post(aURL, headers=lHeaders, data=aData) 92 | print ("+++") 93 | 94 | # If we don't get a 200 HTML response code, we didn't get a result. 95 | if( aResult.status_code != 200 ): 96 | printmessageblock ('* Failed to get a response to call for list_shared_links. \nWe got an error [%s] with text "%s"' % (aResult.status_code, aResult.text)) 97 | return []; 98 | 99 | # Note the JSON response 100 | userLinks = aResult.json() 101 | 102 | # Iterate over the links 103 | for aUserLink in userLinks['links']: 104 | aName = aUserLink['name'] 105 | aLink = aUserLink['path_lower'] 106 | expires = aUserLink.get('expires', '') 107 | 108 | info = [ 109 | aMember[0], 110 | aUserLink['team_member_info']['display_name'], 111 | aUserLink['.tag'], 112 | aName, 113 | aLink, 114 | aUserLink['url'], 115 | aUserLink['id'], 116 | aUserLink['link_permissions']['resolved_visibility']['.tag'], 117 | expires] 118 | memberSharedLinks.append(info) 119 | 120 | hasMore = userLinks['has_more'] # Note if there's another cursor call to make. 121 | if (hasMore == True): 122 | aData = json.dumps({'cursor': userLinks['cursor']}) 123 | 124 | timestop = datetime.datetime.fromtimestamp(time.time()) 125 | print ( '/list_shared_links Time taken: %s seconds' % (timestop-timestart).total_seconds()) 126 | return memberSharedLinks; 127 | 128 | """ 129 | ############################################# 130 | # Step 0 131 | # Clear the terminal window, not essential but makes it easier to read this way. 132 | ############################################# 133 | """ 134 | 135 | os.system('cls' if os.name=='nt' else 'clear') 136 | 137 | 138 | 139 | """ 140 | ############################################# 141 | # Step 1 142 | # 1. Check if there 'aToken' variable is set 143 | # 2. If not, ask the user to enter it. 144 | ############################################# 145 | """ 146 | if (aTokenTMFA == ''): 147 | aTokenTMFA = input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 148 | 149 | 150 | if (aTokenTMM == ''): 151 | aTokenTMM = input('Enter your Dropbox Business API App token (Team Member Management permission): ') 152 | 153 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % aTokenTMM} 154 | 155 | 156 | 157 | """ 158 | ############################################# 159 | # Step 2 160 | # 1. TODO 161 | ############################################# 162 | """ 163 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 164 | aData = json.dumps({'limit': 300}) 165 | 166 | 167 | 168 | """ 169 | ############################################# 170 | # Step 3 171 | # 1. Get list of all Dropbox Team Members 172 | # 2. Create in memory list of them. 173 | # 3. If they match variable 'filterOut', skip them and move to skipped list. 174 | ############################################# 175 | """ 176 | hasMore = True; 177 | loopCounter = 0 178 | dbxUsers = [] 179 | 180 | print ("> Retrieving Dropbox Users via API") 181 | timestart = datetime.datetime.fromtimestamp(time.time()) 182 | 183 | while hasMore: 184 | 185 | print (".") 186 | """ Make the API call """ 187 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 188 | 189 | print ("...") 190 | 191 | # If we don't get a 200 HTML response code, we didn't get a result. 192 | if( aResult.status_code != 200 ): 193 | print ('>>> Failed to get a response to call for /team/members/list') 194 | print (aResult.text) 195 | exit(); 196 | 197 | # Note the JSON response 198 | members = aResult.json() 199 | 200 | # Iterate over the Members in the JSON 201 | for aMember in members['members']: 202 | dbxUsers.append( [aMember['profile']['email'], aMember['profile']['team_member_id']] ) 203 | 204 | hasMore = members['has_more'] # Note if there's another cursor call to make. 205 | 206 | # If it's the first run, from this point onwards the API call is the /continue version. 207 | if ( loopCounter >= 0 ): 208 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 209 | aData = json.dumps({'cursor': members['cursor']}) 210 | loopCounter += 1 211 | 212 | timestop = datetime.datetime.fromtimestamp(time.time()) 213 | print (" We have the Dropbox users in memory from " + str(loopCounter) + " API Calls. it took " + str((timestop-timestart).total_seconds())+ " seconds.") 214 | 215 | 216 | 217 | """ 218 | ############################################# 219 | # Step 4 220 | # 1. Reset for calls to /list_shared_links 221 | ############################################# 222 | """ 223 | 224 | # Open a file to write to 225 | newFileName = ("%s-" + fileName) % getTimeYMDHM() 226 | 227 | with open( newFileName, 'wt') as csvfile: 228 | # Define the delimiter 229 | writer = csv.writer(csvfile, delimiter=',') 230 | # Write the Column Headers 231 | writer.writerow(['email','User Name','Type','Item Name','Path','Share Link URL','Share ID','Link Permission','Expires']) 232 | 233 | # Iterate over the members 234 | for aMember in dbxUsers: 235 | 236 | # if ( aMember[0] == 'jeremy@hanfordinc.com'): 237 | result = getTeamMemberSharedLinks( aMember ) 238 | 239 | for item in result: 240 | writer.writerow(item) 241 | 242 | 243 | 244 | 245 | 246 | """ 247 | ############################################# 248 | # Step 5 249 | # 1. Output how long the script took to run. 250 | ############################################# 251 | """ 252 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 253 | printmessageblock( " Script finished running, it took %s seconds." % ((totalTimeStop-totalTimeStart).total_seconds() ) ) 254 | 255 | -------------------------------------------------------------------------------- /Sharing/list_delete_public_dbx_links.py: -------------------------------------------------------------------------------- 1 | '''This script will either list or delete all publically 2 | accessible shared links which do not have a password''' 3 | 4 | import dropbox 5 | 6 | def getmembers(): 7 | '''get all member id's on a team''' 8 | 9 | # if team is > 1000, also use members/list/continue 10 | members = dbxt.team_members_list().members 11 | membersinfo = [(member.profile.team_member_id, member.profile.email) 12 | for member in members] 13 | 14 | return membersinfo 15 | 16 | def getlinks(userid): 17 | '''get all public links for an individual member''' 18 | 19 | links = dbxt.as_user(userid).sharing_list_shared_links().links 20 | linkurls = [link for link in links 21 | if link.link_permissions.resolved_visibility.is_public()] 22 | 23 | return linkurls 24 | 25 | # def dellinks(userid): 26 | # '''delete all public links for an individual member''' 27 | 28 | # for link in getlinks(userid): 29 | # dbxt.as_user(userid).sharing_revoke_shared_link(link.url) 30 | # print(" %s has been deleted " % link.url) 31 | 32 | # def delall(): 33 | # '''delete all public links for all members''' 34 | 35 | # for (memberid, email) in getmembers(): 36 | # dellinks(memberid) 37 | 38 | def listlinks(): 39 | '''print all public links urls for all members''' 40 | 41 | for (memberid, email) in getmembers(): 42 | links = getlinks(memberid) 43 | link_count = len(links) 44 | print("Public Links: %s User: %s" % (link_count, email)) 45 | for link in links: 46 | print(" %s" % link.url) 47 | 48 | if __name__ == '__main__': 49 | print("This script requires a API token with 'Team Member File Access premissions'") 50 | token = (input("Enter your token: ")) 51 | mode = (input("Enter a mode (either 'list' or 'delete'): ")) 52 | 53 | dbxt = dropbox.DropboxTeam(token) 54 | 55 | if mode == "list": 56 | listlinks() 57 | 58 | ## CAUTION: Enabling this mode will allow deletion of unprotected shared links 59 | ## This could result in disruption to your team 60 | # elif mode == "delete": 61 | # delall() 62 | 63 | else: 64 | print("Please enter a mode of list or delete") 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Sharing/reports/Classes.py: -------------------------------------------------------------------------------- 1 | import pprint # Allows Pretty Print of JSON 2 | 3 | ############################################# 4 | # Define a Class to represent a group 5 | ############################################# 6 | class Group: 7 | def __init__(self): 8 | self.group_name = '' 9 | self.group_members = '' 10 | self.group_permission = '' 11 | self.group_type = '' 12 | 13 | def __init__(self, name, members_count, permision, group_type): 14 | self.group_name = name 15 | self.group_members = members_count 16 | self.group_permission = permision 17 | self.group_type = group_type 18 | 19 | 20 | ############################################# 21 | # Define a Class to represent a user 22 | ############################################# 23 | class User: 24 | def __init__(self): 25 | self.user_access_type = '' 26 | self.user_permission_inherited = '' 27 | self.user_email = '' 28 | self.user_on_team = '' 29 | self.user_name = '' 30 | 31 | def __init__(self, user_access_type, user_permission_inherited, user_email, user_on_team, user_name): 32 | self.user_access_type = user_access_type 33 | self.user_permission_inherited = user_permission_inherited 34 | self.user_email = user_email 35 | self.user_on_team = user_on_team 36 | self.user_name = user_name 37 | 38 | 39 | ############################################# 40 | # Define a Class to represent a shared folder 41 | ############################################# 42 | class SharedFolder: 43 | def __init__(self): 44 | self.team_member_id = '' 45 | self.team_member_name = '' 46 | self.team_member_email = '' 47 | self.email_is_verified = '' 48 | self.account_status = '' 49 | self.is_team_folder = '' 50 | self.is_part_of_team_folder = '' 51 | self.folder_name = '' 52 | self.share_folder_id = '' 53 | self.time_invited = '' 54 | self.path_lower = '' 55 | self.mount_status = '' # If path_lower is empty folder is UNMOUNTED 56 | self.preview_url = '' 57 | self.folder_permission = '' 58 | self.groups = [] # List of Groups with access 59 | self.invitees = [] # List of Invited users 60 | self.users = [] # List of Users with access 61 | 62 | 63 | def getPathLower(self): 64 | if( self.path_lower == None ): 65 | return '' 66 | else: 67 | return self.path_lower 68 | 69 | def addGroup(self, name, members_count, permision, group_type): 70 | grp = Group( name, members_count, permision, group_type ) 71 | self.groups.append( grp ) 72 | 73 | def addUser(self, user_access_type, user_permission_inherited, user_email, user_on_team, user_name): 74 | usr = User( user_access_type, user_permission_inherited, user_email, user_on_team, user_name) 75 | self.users.append( usr ) 76 | 77 | def getUsers(self): 78 | #pprint.pprint ( len( self.users )) 79 | return self.users 80 | 81 | def getExternallyOwnedFolderRow(self): 82 | row = [] 83 | 84 | extUser = None 85 | 86 | # Find the user that is external owner 87 | for aUser in self.users: 88 | #print ( 'aUser: %s | %s | %s | %s' % (aUser.user_name, aUser.user_email, aUser.user_access_type, aUser.user_on_team)) 89 | if ( aUser.user_access_type == 'owner' and aUser.user_on_team == False ): 90 | extUser = aUser 91 | break 92 | 93 | row.append( '' if (extUser == None or extUser.user_email == None) else extUser.user_email ) #self.team_member_email #'Owner email' 94 | row.append( '' if (extUser == None or extUser.user_name == None) else extUser.user_name ) #self.team_member_name #'Owner Name', 95 | row.append( self.folder_name ) #'Folder Name' 96 | row.append( self.getPathLower() ) #'Folder Path' 97 | row.append( self.share_folder_id ) #'Share Folder ID' 98 | row.append( self.mount_status ) #'Folder Mount Status' 99 | row.append( self.team_member_email ) #'User Email' 100 | row.append( self.folder_permission ) #'User Access Type' 101 | row.append( str(False) ) #'User on Team' 102 | row.append( '' ) #'Group Name' 103 | row.append( '' ) #'Group Members' 104 | row.append( '' ) #'Group Permission' 105 | row.append( '' ) #'Group Type' 106 | row.append( str(False) ) # 'Team owned folder' 107 | 108 | return row 109 | 110 | 111 | def getOwnerOwnedFolderRows(self): 112 | 113 | rows = [] 114 | 115 | # Build a list sharing 116 | # One row per groups 117 | for grp in self.groups: 118 | row = [] 119 | 120 | 121 | row.append( self.team_member_email ) #'Owner email' 122 | row.append( self.team_member_name ) #'Owner Name', 123 | row.append( self.folder_name ) #'Name' 124 | row.append( self.getPathLower() ) #'Folder Path' 125 | row.append( self.share_folder_id ) # 'Share Folder ID' 126 | row.append( self.mount_status ) #'Folder Mount Status' 127 | row.append( '' ) # Collaborator Email 128 | row.append( '' ) # Collaborator Permissions 129 | row.append( '' ) # Collaborator on Team 130 | row.append( grp.group_name ) #'Group Name' 131 | row.append( str(grp.group_members) ) #'Group Members' 132 | row.append( grp.group_permission ) #'Group Permission' 133 | row.append( grp.group_type ) #'Group Type' 134 | row.append( str(True) ) # 'Team owned folder' 135 | 136 | rows.append ( row ) 137 | 138 | # One row per user 139 | for aUser in self.users: 140 | row = [] 141 | 142 | row.append( self.team_member_email ) #'Owner email', 143 | row.append( self.team_member_name ) #'Owner Name', 144 | row.append( self.folder_name ) #'Name' 145 | row.append( self.getPathLower() ) #'Folder Path' 146 | row.append( self.share_folder_id ) #'Share Folder ID' 147 | row.append( self.mount_status ) #'Folder Mount Status' 148 | row.append( aUser.user_email ) #'User Email' 149 | row.append( aUser.user_access_type ) #'User Access Type' 150 | row.append( str(aUser.user_on_team) ) #'User on Team' 151 | row.append( '' ) #'Group Name' 152 | row.append( '' ) #'Group Members' 153 | row.append( '' ) #'Group Permission' 154 | row.append( '' ) #'Group Type' 155 | row.append( str(True) ) # 'Team owned folder' 156 | 157 | rows.append ( row ) 158 | 159 | return rows 160 | 161 | # Method to check if this folder is owned by the user 162 | def isOwnedByUser(self): 163 | return self.folder_permission == 'owner' 164 | 165 | # Method to check if this folder is shared from within a Team Folder 166 | def isNestedInTeamFolder(self): 167 | return self.is_part_of_team_folder 168 | 169 | # Method to check if this folder is owned by a team member 170 | def isOwnedByTeamMember(self): 171 | 172 | # Check that User is owner of folder 173 | if ( self.isOwnedByUser() ): 174 | return False 175 | 176 | for aUser in self.users: 177 | if ( aUser.user_access_type == 'owner' and aUser.user_on_team == True ): 178 | return True 179 | 180 | return False 181 | -------------------------------------------------------------------------------- /Sharing/sharingAPI.py: -------------------------------------------------------------------------------- 1 | def create_shared_folder(token, creator, path): 2 | url = 'https://api.dropboxapi.com/1/shared_folders/' 3 | headers = {'Authorization': 'Bearer %s' % token, 'X-Dropbox-Perform-As-Team-Member': creator} 4 | params = {'path': path} 5 | 6 | r = requests.post(url, headers=headers, params=params) 7 | 8 | if r.status_code == 200: 9 | return r.json()['shared_folder_id'] 10 | else: 11 | print 'HTTP error %s (%s - %s)' % (r.status_code, r.reason, r.text) 12 | return False 13 | 14 | def invite_to_shared_folder(token, inviter, invitee, invitee_role, folder_id): 15 | url = 'https://api.dropboxapi.com/1/shared_folders/%s/invitations' % folder_id 16 | headers = {'Authorization': 'Bearer %s' % token, 'X-Dropbox-Perform-As-Team-Member': inviter} 17 | invitee = json.dumps([u'%s' % invitee]) 18 | data = {'uids': invitee, 'suppress_notifications': True, 'invitee_role': invitee_role} 19 | 20 | r = requests.post(url, headers=headers, data=data) 21 | 22 | if r.status_code == 200: 23 | return r.json()['invitations'][0]['invite_id'] 24 | else: 25 | print 'HTTP error %s (%s - %s)' % (r.status_code, r.reason, r.text) 26 | return False 27 | 28 | def accept_invitation(token, invitee, invitation): 29 | url = 'https://api.dropboxapi.com/1/invitations/%s/accept' % invitation 30 | headers = {'Authorization': 'Bearer %s' % token, 'X-Dropbox-Perform-As-Team-Member': invitee} 31 | data = {} 32 | 33 | r = requests.post(url, headers=headers, data=data) 34 | 35 | if r.status_code == 200: 36 | return True 37 | else: 38 | print 'HTTP error %s (%s - %s)' % (r.status_code, r.reason, r.text) 39 | return False 40 | 41 | def unshare_folder(token, owner, folder): 42 | url = 'https://api.dropboxapi.com/1/shared_folders/%s/unshare' % folder 43 | headers = {'Authorization': 'Bearer %s' % token, 'X-Dropbox-Perform-As-Team-Member': owner} 44 | data = {'keep_files': True} 45 | 46 | r = requests.post(url, headers=headers, data=data) 47 | 48 | if r.status_code == 200: 49 | return True 50 | else: 51 | print 'HTTP error %s (%s - %s)' % (r.status_code, r.reason, r.text) 52 | return False 53 | 54 | def get_member_ids(token): 55 | url = 'https://api.dropbox.com/1/team/members/list' 56 | headers = {'Authorization': 'Bearer %s' % token, 'Content-Type': 'application/json'} 57 | data = {} 58 | members = [] 59 | 60 | r = requests.post(url, headers=headers, data=json.dumps(data)) 61 | 62 | if r.status_code == 200: 63 | profiles = r.json()['members'] 64 | for i in profiles: 65 | members.append([i['profile']['email'], i['profile']['member_id']]) 66 | else: 67 | print 'HTTP error %s (%s - %s)' % (r.status_code, r.reason, r.text) 68 | 69 | return members 70 | 71 | def find_member(email, member_list): 72 | try: 73 | user = next(subl for subl in member_list if email in subl) 74 | return user[1] 75 | except StopIteration: 76 | return False -------------------------------------------------------------------------------- /Users/DeprovisionFromCsv.py: -------------------------------------------------------------------------------- 1 | # Deprovision users from CSV with option to change their email and let the keep their account as Dropbox Basic 2 | 3 | import urllib2 4 | import json 5 | import re 6 | import csv 7 | import argparse 8 | 9 | # Command line arguments 10 | parser = argparse.ArgumentParser(description='Removes Dropbox Business members from a CSV file.') 11 | parser.add_argument('file', type=argparse.FileType('r'), help='CSV File of users to provision. ' 12 | 'Format is [email,keep account (true/false),' 13 | 'new email (optional)]') 14 | args = parser.parse_args() 15 | 16 | token = raw_input('Enter your Dropbox Business API App token (Team Member Management permission): ') 17 | 18 | 19 | # set new email for the user to use post deprovision 20 | def setEmail(oldEmail, newEmail): 21 | 22 | data = { 23 | "user": { 24 | ".tag": "email", 25 | "email": oldEmail 26 | }, 27 | "new_email": newEmail 28 | } 29 | 30 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/set_profile', json.dumps(data)) 31 | request.add_header("Authorization", "Bearer " + token) 32 | request.add_header("Content-type", 'application/json') 33 | 34 | try: 35 | json.loads(urllib2.urlopen(request).read()) 36 | 37 | # Exit on error here. Probably user not found or bad OAuth token. Show response. 38 | except urllib2.HTTPError, error: 39 | print 'Error setting ' + oldEmail + ' to ' + newEmail + ': ' + str(error.read()) 40 | 41 | 42 | # remove member, letting them keep their account (true/false), and wipe their devices (true/false) 43 | def removeMember(email, keep): 44 | 45 | data = { 46 | "user": { 47 | ".tag": "email", 48 | "email": email 49 | }, 50 | "wipe_data": not keep, 51 | "keep_account": keep 52 | } 53 | 54 | request = urllib2.Request('https://api.dropbox.com/2/team/members/remove', json.dumps(data)) 55 | request.add_header("Authorization", "Bearer " + token) 56 | request.add_header("Content-type", 'application/json') 57 | 58 | try: 59 | response = json.loads(urllib2.urlopen(request).read()) 60 | print 'Deprovisioned ' + email 61 | 62 | # Exit on error here. Probably user not found or bad OAuth token. Show response. 63 | except urllib2.HTTPError, error: 64 | print 'Error deprovisioning ' + email + ': ' + str(error.read()) 65 | 66 | 67 | for row in csv.reader(args.file): 68 | 69 | # Check for 3 columns, make sure first column looks like an email. Terminate with script help if not. 70 | if len(row) < 2: 71 | print "Expected 2-3 column CSV file in the format [email,keep account (true/false),new email (optional)]. " \ 72 | "Error in line " + str(row) 73 | elif not re.match("[^@]+@[^@]+\.[^@]+", row[0]): 74 | print "Invalid email in line [" + str(row) + "]" 75 | elif len(row) == 3 and len(row[2]) > 0 and not re.match("[^@]+@[^@]+\.[^@]+", row[2]): 76 | print "Invalid new email in line [" + str(row) + "]" 77 | else: 78 | 79 | # hold onto email that needs to be removed 80 | email = row[0] 81 | 82 | # if there's a new email address, change the email first 83 | if len(row) > 2 and len(row[2]) > 0: 84 | setEmail(row[0], row[2]) 85 | email = row[2] 86 | 87 | # Remove the member 88 | removeMember(email, str.lower(row[1]).strip() == 'true') 89 | 90 | -------------------------------------------------------------------------------- /Users/GetUserList.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import os # Allows for the clearing of the Terminal Window 4 | import csv # Allows outputting to CSV file 5 | import time, datetime 6 | 7 | """ 8 | ******************************************************************************************************************** 9 | 10 | The intention of this script is to: 11 | 12 | Pull out and generate a CSV listing of all the members of your Dropbox Team. 13 | The script requires you to set the variable aTokenTMM equal to your Team Member Management OAuth token. 14 | 15 | The outputted CSV will have the 16 | 'First name', 17 | 'Last name', 18 | 'Email address', 19 | 'Account status', 20 | 'Role', 21 | 'Team member ID', 22 | 'External ID', 23 | 'Email verified', 24 | 'Joined on' 25 | 26 | Requirements: 27 | Script tested on Python 3.6.5 28 | 29 | One Dropbox API Token is needed, inserted just below this comments section. 30 | * Team Member Management 31 | 32 | Pre-requisites: 33 | * Scripts requires library 'Requests' - You can install using "pip install requests" 34 | 35 | ******************************************************************************************************************** 36 | """ 37 | 38 | 39 | 40 | """ 41 | Set your OAuth Token here with 'Team Member Management' permissions 42 | """ 43 | 44 | aTokenTMM = '' # Team Member Management 45 | 46 | """ 47 | ******************************************************************************************************************** 48 | DO NOT EDIT BELOW THIS POINT 49 | ******************************************************************************************************************** 50 | """ 51 | 52 | ############################################# 53 | # Function to return current Timestamp 54 | ############################################# 55 | def getTimeYMD(): 56 | lRightNow = datetime.datetime.fromtimestamp(time.time()).strftime('%y%m%d-%H-%M-%S') 57 | return lRightNow; 58 | 59 | ############################################# 60 | # Function to print Message to console in a tidy box 61 | ############################################# 62 | def printmessageblock( str ): 63 | print ("\n*********************************************************") 64 | print ("* %s" % (str)) 65 | print ("*********************************************************\n") 66 | return; 67 | 68 | ############################################# 69 | # Function to print Message to console in a tidy box 70 | ############################################# 71 | def getTimeInHoursMinutesSeconds( sec ): 72 | return time.strftime("%H hrs %M mins %S sec", time.gmtime(sec)) 73 | 74 | 75 | 76 | 77 | 78 | """ 79 | DO NOT EDIT BELOW THIS POINT 80 | """ 81 | 82 | totalTimeStart = datetime.datetime.fromtimestamp(time.time()) 83 | 84 | 85 | fileName = "User List.csv" # Do not place a path 86 | aURL = 'https://api.dropboxapi.com/2/team/members/list' 87 | aData = json.dumps({'limit': 200, 'include_removed': True}) 88 | 89 | 90 | """ 91 | ############################################# 92 | # Step 0 93 | # Clear the terminal window, not essential but makes it easier to read this way. 94 | ############################################# 95 | """ 96 | 97 | os.system('cls' if os.name=='nt' else 'clear') 98 | 99 | 100 | """ 101 | ############################################# 102 | # Step 1 103 | # 1. Check if there 'aTokenTMM' variable is set 104 | # 2. If not, ask the user to enter it. 105 | ############################################# 106 | """ 107 | if (aTokenTMM == ''): 108 | aTokenTMM = raw_input('Enter your Dropbox Business API App token (Team Member Management permission): ') 109 | 110 | aHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % aTokenTMM} 111 | 112 | 113 | 114 | 115 | """ 116 | ############################################# 117 | # Step 2 118 | # 1. Open a CSV File 119 | # 2. Iterate over the list and populate the file 120 | ############################################# 121 | """ 122 | 123 | fileName = ("%s " + fileName) % getTimeYMD() 124 | hasMore = True; 125 | loopCounter = 0; 126 | totalMembers = 0 127 | 128 | with open( fileName, 'w') as csvfile: 129 | writer = csv.writer(csvfile, delimiter=',') 130 | # Write the Column Headers 131 | writer.writerow(['First name','Last name','Email address', 'Account status', 'Role', 'Team member ID', 'External ID', 'Email verified', 'Joined on']) 132 | 133 | while hasMore: 134 | """ Make the API call """ 135 | print (">>> API call") 136 | aResult = requests.post(aURL, headers=aHeaders, data=aData) 137 | print ("<<< Results") 138 | 139 | 140 | # If we don't get a 200 HTML response code, we didn't get a result. 141 | if( aResult.status_code != 200 ): 142 | printmessageblock ('* Failed to get a response to call for /team/members/list') 143 | exit(); 144 | 145 | # Note the JSON response 146 | members = aResult.json() 147 | totalMembers += len(members['members']) # Keep a count of total members ( this will be verfied and unverfied accounts ) 148 | 149 | # Iterate over the Members in the JSON 150 | for aMember in members['members']: 151 | 152 | externalID = "" 153 | if( 'external_id' in aMember['profile'] ): 154 | externalID = aMember['profile']['external_id'].strip() 155 | 156 | joinedOnDay = "" 157 | if( 'joined_on' in aMember['profile'] ): 158 | joinedOnDay = aMember['profile']['joined_on'].strip() 159 | 160 | writer.writerow([aMember['profile']['name']['given_name'].strip(), 161 | aMember['profile']['name']['surname'].strip(), 162 | aMember['profile']['email'].strip(), 163 | aMember['profile']['status']['.tag'].strip(), 164 | aMember['role']['.tag'].strip(), 165 | aMember['profile']['team_member_id'].strip(), 166 | externalID, 167 | aMember['profile']['email_verified'], 168 | joinedOnDay]) 169 | 170 | hasMore = members['has_more'] # Note if there's another cursor call to make. 171 | 172 | # If it's the first run, from this point onwards the API call is the /continue version. 173 | if ( loopCounter == 0 ): 174 | aURL = 'https://api.dropboxapi.com/2/team/members/list/continue' 175 | aData = json.dumps({'cursor': members['cursor']}) 176 | 177 | # Increment the loop coun 178 | # End of while hasMore: 179 | 180 | print ('\n\nTotal Members found: %s' % totalMembers) 181 | 182 | """ 183 | ############################################# 184 | # Step 5 185 | # 1. Output how long the script took to run. 186 | ############################################# 187 | """ 188 | totalTimeStop = datetime.datetime.fromtimestamp(time.time()) 189 | totalTimeInSeconds = (totalTimeStop-totalTimeStart).total_seconds() 190 | timeAsStr = getTimeInHoursMinutesSeconds( totalTimeInSeconds ) 191 | printmessageblock( " Script finished running, it took %s seconds." % ( timeAsStr ) ) 192 | -------------------------------------------------------------------------------- /Users/ListMembers.ps1: -------------------------------------------------------------------------------- 1 | # Prompt for Team Member Management permission 2 | $token = Read-Host -Prompt "Enter your Dropbox Business API App token (Team Member Management permission): " 3 | $token = "Bearer $token" 4 | 5 | $object = New-Object psobject 6 | $has_more = $true 7 | $cursor = $null 8 | 9 | Write-Host 10 | Write-Host "Team Members:" -ForegroundColor Red 11 | Write-Host 12 | 13 | # Continue to call get_events as long as there are more events 14 | while($has_more) { 15 | 16 | if($cursor) { 17 | if($object.cursor) { 18 | $object.cursor = $cursor 19 | } else { 20 | $object | Add-Member -MemberType NoteProperty -Name cursor -Value $cursor 21 | } 22 | } 23 | 24 | # Make API call 25 | $teammembers = Invoke-RestMethod -Uri https://api.dropbox.com/1/team/members/list -Body (ConvertTo-Json $object) -ContentType application/json -Headers @{ 26 | Authorization = $token } -Method Post 27 | $memberCount = 0 28 | 29 | # For every member in the team, display their user details 30 | foreach ($member in $teammembers.members) 31 | { 32 | $given_name = $teammembers.members.Item($memberCount).profile.given_name 33 | $surname = $teammembers.members.Item($memberCount).profile.surname 34 | $status = $teammembers.members.Item($memberCount).profile.status 35 | $member_id = $teammembers.members.Item($memberCount).profile.member_id 36 | $email = $teammembers.members.Item($memberCount).profile.email 37 | $external_id = $teammembers.members.Item($memberCount).profile.external_id 38 | $groups = $teammembers.members.Item($memberCount).profile.groups 39 | $admin = $teammembers.members.Item($memberCount).permissions.is_admin 40 | 41 | #display 42 | Write-Host "Name:" $given_name $surname -ForegroundColor Green 43 | Write-Host "Status:" $status 44 | Write-Host "Member_Id:" $member_id 45 | Write-Host "Email:" $email 46 | Write-Host "External_Id:" $external_id 47 | Write-Host "Groups:" $groups 48 | Write-Host "Admin:" $admin 49 | Write-Host 50 | 51 | $memberCount++ 52 | } 53 | 54 | $has_more = [System.convert]::ToBoolean($teammembers.has_more) 55 | $cursor = $teammembers.cursor 56 | 57 | } -------------------------------------------------------------------------------- /Users/ListMembers.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import argparse 4 | import csv 5 | import sys 6 | 7 | reload(sys) 8 | sys.setdefaultencoding('UTF8') 9 | 10 | parser = argparse.ArgumentParser(description='Lists members on a Dropbox for Business Team') 11 | parser.add_argument( '-q', '--quota', action='store_const', const=True, default=False, dest='quota', 12 | help='Include usage quota statistics - may increase script time to completion') 13 | parser.add_argument( '-l', '--links', action='store_const', const=True, default=False, dest='links', 14 | help='Include shared link count - may increase script time to completion') 15 | parser.add_argument( '-f', '--folders', action='store_const', const=True, default=False, dest='folders', 16 | help='Include shared folder count - may increase script time to completion') 17 | 18 | args = parser.parse_args() 19 | 20 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 21 | 22 | # Get all DfB members, paging through results if necessary 23 | def getDfbMembers(cursor): 24 | data = {"limit":100} 25 | if cursor is not None: 26 | data["cursor"] = cursor 27 | 28 | request = urllib2.Request('https://api.dropbox.com/1/team/members/list', json.dumps(data)) 29 | request.add_header("Authorization", "Bearer "+dfbToken) 30 | request.add_header("Content-type", 'application/json') 31 | try: 32 | response = json.loads(urllib2.urlopen(request).read()) 33 | members = response["members"] 34 | 35 | if response["has_more"]: 36 | members = members + getDfbMembers(cursor=response["cursor"]) 37 | 38 | return members 39 | 40 | # Exit on error here. Probably bad OAuth token. Show DfB response. 41 | except urllib2.HTTPError, error: 42 | parser.error(error.read()) 43 | 44 | # Get a member's info (account details, quota usage) 45 | def getMemberInfo(memberId): 46 | request = urllib2.Request('https://api.dropboxapi.com/1/account/info') 47 | request.add_header("Authorization", "Bearer "+dfbToken) 48 | request.add_header("Content-type", 'application/json') 49 | request.add_header("X-Dropbox-Perform-As-Team-Member", memberId) 50 | 51 | try: 52 | return json.loads(urllib2.urlopen(request).read()) 53 | except urllib2.HTTPError, error: 54 | parser.error(error.read()) 55 | 56 | # Get a dict of groupid - group name 57 | def getGroups(): 58 | request = urllib2.Request('https://api.dropbox.com/1/team/groups/list', json.dumps({})) 59 | request.add_header("Authorization", "Bearer "+dfbToken) 60 | request.add_header("Content-type", 'application/json') 61 | 62 | try: 63 | ret = {} 64 | for group in json.loads(urllib2.urlopen(request).read())["groups"]: 65 | ret[group["group_id"]] = group["group_name"] 66 | return ret 67 | except urllib2.HTTPError, error: 68 | parser.error(error.read()) 69 | 70 | # Get the count of shared links for the member 71 | def countSharedLinks(memberId): 72 | cursor = None 73 | count = 0 74 | 75 | try: 76 | while True: 77 | params = {} 78 | if cursor is not None: 79 | params['cursor'] = cursor 80 | request = urllib2.Request('https://api.dropboxapi.com/2/sharing/list_shared_links', json.dumps(params)) 81 | request.add_header("Authorization", "Bearer "+dfbToken) 82 | request.add_header("Dropbox-API-Select-User", memberId) 83 | request.add_header("Content-Type", "application/json") 84 | response_string = urllib2.urlopen(request).read() 85 | response = json.loads(response_string) 86 | count = count + len(response["links"]) 87 | if not response['has_more']: 88 | break 89 | cursor = response['cursor'] 90 | except Exception as e: 91 | return "ERROR" 92 | 93 | return count 94 | 95 | # Get the count of shared folders for the member 96 | def countSharedFolders(memberId): 97 | cursor = None 98 | count = 0 99 | owner = 0 100 | 101 | try: 102 | while True: 103 | params = {} 104 | 105 | url = 'https://api.dropboxapi.com/2/sharing/list_folders' 106 | if cursor is not None: 107 | params['cursor'] = cursor 108 | url = 'https://api.dropboxapi.com/2/sharing/list_folders/continue' 109 | 110 | request = urllib2.Request(url, json.dumps(params)) 111 | request.add_header("Authorization", "Bearer "+dfbToken) 112 | request.add_header("Dropbox-API-Select-User", memberId) 113 | request.add_header("Content-Type", "application/json") 114 | response_string = urllib2.urlopen(request).read() 115 | response = json.loads(response_string) 116 | count = count + len(response["entries"]) 117 | for entry in response["entries"]: 118 | if entry["access_type"][".tag"] == 'owner': 119 | owner = owner + 1 120 | 121 | if not 'cursor' in response: 122 | break 123 | cursor = response['cursor'] 124 | 125 | except Exception as e: 126 | return {"total":"ERROR", "owner":"ERROR", "member":"ERROR"} 127 | 128 | return {"total":count, "owner":owner, "member":(count-owner)} 129 | 130 | def formatSize(num, suffix='B'): 131 | for unit in ['','K','M','G','T','P','E','Z']: 132 | if abs(num) < 1000.0: 133 | return "%3.1f%s%s" % (num, unit, suffix) 134 | num /= 1024.0 135 | return "%.1f%s%s" % (num, 'Yi', suffix) 136 | 137 | 138 | csvwriter = csv.writer(sys.stdout) 139 | 140 | header = ['Email', 'First Name', 'Last Name', 'Status', 'Groups'] 141 | 142 | if args.quota: 143 | header = header + ['Locale', 'Normal Usage', 'Normal Usage (bytes)', 'Team Shared Usage', 'Team Shared Usage (bytes)'] 144 | if args.links: 145 | header = header + ['Shared Links'] 146 | if args.folders: 147 | header = header + ['Shared Folders (Total)', 'Shared Folders (Owner)', 'Shared Folders (Member)'] 148 | 149 | 150 | csvwriter.writerow(header) 151 | 152 | groupMap = getGroups() 153 | 154 | for member in getDfbMembers(None): 155 | 156 | # Get the group names from the ID array 157 | groupstr = '' 158 | if 'groups' in member["profile"]: 159 | for group in member["profile"]["groups"]: 160 | if group in groupMap: 161 | if groupstr != '': 162 | groupstr = groupstr + ", " 163 | groupstr = groupstr + groupMap[group] 164 | 165 | member_row = [member["profile"]["email"], \ 166 | member["profile"]["given_name"], \ 167 | member["profile"]["surname"], \ 168 | member["profile"]["status"], 169 | groupstr] 170 | 171 | # Member info & quota 172 | if args.quota: 173 | if member["profile"]["status"] == "active": 174 | info = getMemberInfo(member["profile"]["member_id"]) 175 | member_row = member_row + [info["locale"], \ 176 | formatSize(info["quota_info"]["normal"]), \ 177 | str(info["quota_info"]["normal"]), \ 178 | formatSize(info["quota_info"]["shared"]), \ 179 | str(info["quota_info"]["shared"])] 180 | else: 181 | member_row = member_row + ['-', '-', '-', '-', '-'] 182 | 183 | # Shared links count 184 | if args.links: 185 | if member["profile"]["status"] == "active": 186 | member_row = member_row + [countSharedLinks(member["profile"]["member_id"])] 187 | else: 188 | member_row = member_row + ['-'] 189 | 190 | # Shared folder count 191 | if args.folders: 192 | if member["profile"]["status"] == "active": 193 | shares = countSharedFolders(member["profile"]["member_id"]) 194 | member_row = member_row + [shares["total"], shares["owner"], shares["member"]] 195 | else: 196 | member_row = member_row + ['-', '-', '-'] 197 | 198 | csvwriter.writerow(member_row) 199 | -------------------------------------------------------------------------------- /Users/ListPendingMembersInvitedBeforeDate.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import argparse 4 | import sys 5 | import time 6 | import datetime 7 | 8 | reload(sys) 9 | sys.setdefaultencoding('UTF8') 10 | 11 | parser = argparse.ArgumentParser(description='List pending members whom were invited prior to a specified date') 12 | parser.add_argument('-d', '--date', dest='date', help='List members invited prior to this date (yyyy-mm-dd format)',required=True) 13 | args = parser.parse_args() 14 | 15 | # parse to utc datetime 16 | utcdate = int(time.mktime(datetime.datetime.strptime(args.date, "%Y-%m-%d").timetuple())) * 1000 17 | 18 | #ask for audit token 19 | dfbAuditToken = raw_input('Enter your Dropbox Business API App token (Team Auditing permission): ') 20 | 21 | # Get all DfB members, paging through results if necessary 22 | def getDfbMembers(cursor): 23 | data = {"limit":100} 24 | 25 | if cursor is not None: 26 | data["cursor"] = cursor 27 | 28 | request = urllib2.Request('https://api.dropbox.com/1/team/members/list', json.dumps(data)) 29 | request.add_header("Authorization", "Bearer "+dfbAuditToken) 30 | request.add_header("Content-type", 'application/json') 31 | try: 32 | response = json.loads(urllib2.urlopen(request).read()) 33 | members = response["members"] 34 | 35 | if response["has_more"]: 36 | members = members + getDfbMembers(cursor=response["cursor"]) 37 | 38 | return members 39 | 40 | # Exit on error here. Probably bad OAuth token. Show DfB response. 41 | except urllib2.HTTPError, error: 42 | parser.error(error.read()) 43 | 44 | 45 | # find invites before a certain date as UTC timestamp 46 | def getInvitesBeforeDate(date, invites, cursor): 47 | data = { "category": "members", "limit": 1000, "end_ts": date } 48 | 49 | if cursor is not None: 50 | data["cursor"] = cursor 51 | 52 | request = urllib2.Request('https://api.dropbox.com/1/team/log/get_events', json.dumps(data)) 53 | request.add_header("Authorization", "Bearer "+dfbAuditToken) 54 | request.add_header("Content-type", 'application/json') 55 | try: 56 | response = json.loads(urllib2.urlopen(request).read()) 57 | for event in response['events']: 58 | if(event['event_type'] == 'member_invite'): 59 | invites[event['info_dict']['email']] = event 60 | 61 | if response["has_more"]: 62 | getInvitesBeforeDate(date, invites, cursor=response["cursor"]) 63 | 64 | except urllib2.HTTPError, error: 65 | parser.error(error.read()) 66 | 67 | ## To delete the pending invites, uncomment the following method & variable, and the commented section below. Note that delete prompts for a member management API key ## 68 | #dfbManagmentToken = None 69 | #def removeMember(memberId): 70 | # global dfbManagmentToken 71 | # if dfbManagmentToken is None: 72 | # dfbManagmentToken = raw_input('Enter your Dropbox Business API App token (Member Management permission): ') 73 | # 74 | # data = {"member_id": memberId} 75 | # request = urllib2.Request('https://api.dropbox.com/1/team/members/remove', json.dumps(data)) 76 | # request.add_header("Authorization", "Bearer "+dfbManagmentToken) 77 | # request.add_header("Content-type", 'application/json') 78 | # try: 79 | # response = json.loads(urllib2.urlopen(request).read()) 80 | # except urllib2.HTTPError, error: 81 | # parser.error(error.read()) 82 | 83 | 84 | # find all invited members 85 | invitedMembers = [] 86 | print "Getting invited members... " 87 | for member in getDfbMembers(None): 88 | if member['profile']['status'] == 'invited': 89 | invitedMembers.append(member['profile']) 90 | 91 | # get all invite events before a particular date 92 | print "Looking up invitation times..." 93 | invites = dict() 94 | getInvitesBeforeDate(utcdate, invites, None) 95 | 96 | # figure out if there's overlap between invited users & old invitations 97 | invitedBeforeDate = [] 98 | for member in invitedMembers: 99 | if member['email'] in invites: 100 | member['invited'] = invites[member['email']]['time'][0:10] 101 | invitedBeforeDate.append(member) 102 | 103 | # sort by invitation date ascending 104 | invitedBeforeDate.sort(key=lambda x: datetime.datetime.strptime(x['invited'], '%Y-%m-%d')) 105 | 106 | print "-----------------------------------------------------------------------------------" 107 | print "The following "+str(len(invitedBeforeDate)) +" out of "+ str(len(invitedMembers)) +" pending invitations were sent before " + str(args.date) 108 | print "-----------------------------------------------------------------------------------" 109 | 110 | for d in invitedBeforeDate: 111 | print d['email']+" was invited on "+invites[d['email']]['time'][0:10] 112 | 113 | ## To delete the pending invites, uncomment the following section & the above removeMember method ## 114 | #confirm = raw_input(' Delete pending invite? Type "YES" to delete, or enter to continue without deleting: ') 115 | #if confirm == 'YES': 116 | # removeMember(d['member_id']) 117 | # print " (deleted pending invitation to "+d['email']+")" 118 | #else: 119 | # print " (invite was not deleted)" -------------------------------------------------------------------------------- /Users/ProvisionFromCSV.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import re 4 | import csv 5 | import argparse 6 | 7 | # Look up a member id from an email address 8 | def getMemberId(token, email): 9 | lookupRequest = urllib2.Request('https://api.dropboxapi.com/2/team/members/get_info', json.dumps({'members':[{'.tag':'email','email': email}] })) 10 | lookupRequest.add_header("Authorization", "Bearer "+token) 11 | lookupRequest.add_header("Content-type", 'application/json') 12 | profile = json.loads(urllib2.urlopen(lookupRequest).read()) 13 | return profile[0]['profile']['team_member_id'] 14 | 15 | # Give an member admin permissions 16 | def grantAdmin(token, memberId): 17 | if memberId is None: 18 | return 19 | addRequest = urllib2.Request('https://api.dropboxapi.com/2/team/members/set_admin_permissions', json.dumps({'user': { '.tag': 'team_member_id', 'team_member_id': memberId}, 'new_role':'team_admin' })) 20 | addRequest.add_header("Authorization", "Bearer "+token) 21 | addRequest.add_header("Content-type", 'application/json') 22 | urllib2.urlopen(addRequest); 23 | print " granted admin permissions" 24 | 25 | 26 | # Command line arguments 27 | parser = argparse.ArgumentParser(description='Invites Dropbox for Business members from a CSV file.') 28 | parser.add_argument('file', type=argparse.FileType('r'), help='CSV File of users to provision. Format is [email,firstname,lastname]') 29 | parser.add_argument( '-s', '--silent', action='store_const', const=True, default=False, dest='silent', help='Silent invite. Skips welcome email.') 30 | parser.add_argument( '-a', '--admin', action='store_const', const=True, default=False, dest='admin', help='Grants Team Admin permissions to invited members.') 31 | 32 | args = parser.parse_args() 33 | 34 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member Management permission): ') 35 | 36 | if args.silent: 37 | print "Starting silent provision" 38 | else: 39 | print "Starting provision" 40 | 41 | csvreader = csv.reader(args.file) 42 | for row in csvreader: 43 | 44 | # Check for 3 columns, make sure first column looks like an email. Terminate with script help if not. 45 | if not len(row) == 3: 46 | parser.error("Expected 3 column CSV file in the format [email,firstname,lastname]. Error in line "+row) 47 | if not re.match("[^@]+@[^@]+\.[^@]+", row[0]): 48 | line = ','.join(row) 49 | parser.error("Expected 3 column CSV file in the format [email,firstname,lastname]. Invalid email in line ["+line+"]") 50 | 51 | # Add the member 52 | welcome = not args.silent 53 | addRequest = urllib2.Request('https://api.dropboxapi.com/2/team/members/add', json.dumps({ 'new_members':[{ 'member_email': row[0], 'member_given_name': row[1], 'member_surname': row[2], 'send_welcome_email':welcome}], 'force_async': False})) 54 | addRequest.add_header("Authorization", "Bearer "+dfbToken) 55 | addRequest.add_header("Content-type", 'application/json') 56 | 57 | try: 58 | print " inviting "+row[0] 59 | profile = json.loads(urllib2.urlopen(addRequest).read()) 60 | 61 | aTeamMemberID = 0 62 | 63 | # If the member has already been invited 64 | if ( profile['complete'][0]['.tag'] == 'user_already_on_team'): 65 | print " user is already invited to the DfB team" 66 | 67 | member_id = getMemberId(token=dfbToken, email=row[0]) 68 | aTeamMemberID = member_id 69 | 70 | # re-send the invite if we're not in silent mode 71 | if not args.silent: 72 | welcomeRequest = urllib2.Request('https://api.dropboxapi.com/2/team/members/send_welcome_email', json.dumps({'.tag': 'team_member_id','team_member_id': member_id })) 73 | welcomeRequest.add_header("Authorization", "Bearer "+dfbToken) 74 | welcomeRequest.add_header("Content-type", 'application/json') 75 | urllib2.urlopen(welcomeRequest).read() 76 | print " re-sent invitation" 77 | 78 | else: 79 | aTeamMemberID = profile['complete'][0]['profile']['team_member_id'] 80 | 81 | # Apply Team Admin permissions if required 82 | if args.admin: 83 | grantAdmin(token=dfbToken, memberId=aTeamMemberID) 84 | 85 | except urllib2.HTTPError, error: 86 | msg = json.loads(error.read())["error"] 87 | 88 | # If OAuth token is bad (401) or doesn't have member management permission (403), terminate & display script help. 89 | if error.code == 401 or error.code == 403: 90 | parser.error(msg) 91 | 92 | 93 | print "Done" 94 | -------------------------------------------------------------------------------- /Users/RemindPendingMembers.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import urllib2 3 | import json 4 | import argparse 5 | import sys 6 | from collections import Counter 7 | 8 | reload(sys) 9 | sys.setdefaultencoding('UTF8') 10 | 11 | parser = argparse.ArgumentParser(description='Send reminder emails to all invited (but not joined) members.') 12 | 13 | args = parser.parse_args() 14 | 15 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member Management permission): ') 16 | 17 | # Get all DfB members, paging through member list if necessary 18 | def getDfbMembers(cursor): 19 | data = {"limit":100} 20 | if cursor is not None: 21 | data["cursor"] = cursor 22 | 23 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/list', json.dumps(data)) 24 | request.add_header("Authorization", "Bearer "+dfbToken) 25 | request.add_header("Content-type", 'application/json') 26 | try: 27 | response = json.loads(urllib2.urlopen(request).read()) 28 | members = response["members"] 29 | 30 | if response["has_more"]: 31 | members = members + getDfbMembers(cursor=response["cursor"]) 32 | 33 | return members 34 | 35 | # Exit on error here. Probably bad OAuth token. Show DfB response. 36 | except urllib2.HTTPError, error: 37 | parser.error(error.read()) 38 | 39 | # Sends a reminder 40 | def remind(memberId): 41 | params = {'.tag':'team_member_id','team_member_id':memberId} 42 | request = urllib2.Request('https://api.dropboxapi.com/2/team/members/send_welcome_email', data=json.dumps(params)) 43 | request.add_header("Authorization", "Bearer "+dfbToken) 44 | request.add_header("Content-type", 'application/json') 45 | try: 46 | urllib2.urlopen(request).read() 47 | except urllib2.HTTPError, error: 48 | parser.error(error.read()) 49 | 50 | 51 | members = getDfbMembers(None) 52 | 53 | print "Reminding invited members.." 54 | 55 | for member in members: 56 | if member["profile"]["status"][".tag"] == "invited": 57 | print " reminding "+member["profile"]["email"] 58 | remind(member["profile"]["team_member_id"]) 59 | 60 | print "Done" 61 | -------------------------------------------------------------------------------- /Users/SetMemberSpaceLimits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import requests 4 | import requests.exceptions 5 | import json 6 | import ast 7 | import sys 8 | import os 9 | 10 | """ 11 | A script to intake and set all team-members to a specified member space-limitation in GB. 12 | It iterates through every team-member, checks if they have a quota set and whether or not it is of 13 | the specified limit. If not, it will change the member's quota to the specified space-limtation. 14 | 15 | Notes: 16 | - Must have Member Space Limits enabled 17 | - Members' quotas can't be set to under 25 GB. 18 | - Script written and tested using Python 2.7.10 19 | - Will need a Team Member File Access API token (https://www.dropbox.com/developers/apps) 20 | 21 | Uses the following API calls: 22 | - https://api.dropboxapi.com/2/team/members/list 23 | - https://api.dropboxapi.com/2/team/members/list/continue 24 | - https://api.dropboxapi.com/2/team/member_space_limits/get_custom_quota 25 | - https://api.dropboxapi.com/2/team/member_space_limits/set_custom_quota 26 | """ 27 | 28 | reload(sys) 29 | sys.setdefaultencoding('UTF8') 30 | 31 | dfbToken = raw_input('Enter your Dropbox Business API App token (Team Member File Access permission): ') 32 | setLimit = raw_input('Set your preferred Space Limitation (Minimum is 25GB): ') 33 | 34 | if int(setLimit) < 25: 35 | print 'Cannot set limit to less than 25GB' 36 | sys.exit() 37 | 38 | 39 | # ----------- get members ------------ # 40 | def getDfbMembers(): 41 | print 'Working.......................' 42 | data = {'limit': 1000, 'include_removed': False} 43 | HEADERS = {} 44 | HEADERS['Authorization'] = 'Bearer ' +dfbToken 45 | try: 46 | r = requests.post('https://api.dropboxapi.com/2/team/members/list',json = data, headers = HEADERS) 47 | r.raise_for_status() 48 | resp = json.loads(r.text) 49 | members = resp['members'] 50 | cursor = resp['cursor'] 51 | more = resp['has_more'] 52 | data = {'cursor': cursor} 53 | batchCheckQuota(formatMembers(members)) 54 | while more: 55 | print 'Has more, still working.......................' 56 | r_continue = requests.post('https://api.dropboxapi.com/2/team/members/list/continue',json = data, headers = HEADERS) 57 | r_continue.raise_for_status() 58 | resp_continue = json.loads(r_continue.text) 59 | members = resp_continue['members'] 60 | cursor = resp_continue['cursor'] 61 | more = resp_continue['has_more'] 62 | data = {'cursor': cursor} 63 | batchCheckQuota(formatMembers(members)) 64 | except requests.exceptions.HTTPError as e: 65 | sys.stderr.write('ERROR: {}'.format(e)) 66 | sys.stderr.write('\n') 67 | 68 | 69 | # ----------- format members for batch check ------------ # 70 | def formatMembers(members): 71 | dataDict = {} 72 | dataList = [] 73 | for member in members: 74 | tempDict = {} 75 | tempDict['.tag'] = 'email' 76 | tempDict['email'] = member['profile']['email'] 77 | dataList.append(tempDict) 78 | dataDict['users'] = dataList 79 | return dataDict 80 | 81 | 82 | # ----------- batch check quota ------------ # 83 | def batchCheckQuota(membersDict): 84 | data = membersDict 85 | HEADERS = {} 86 | HEADERS['Authorization'] = 'Bearer ' +dfbToken 87 | r = requests.post('https://api.dropboxapi.com/2/team/member_space_limits/get_custom_quota',json = data, headers = HEADERS) 88 | quotas = json.loads(r.text) 89 | dataDict = {} 90 | setQuotaList = [] 91 | for quota in quotas: 92 | if 'quota_gb' in quota: 93 | if (quota['quota_gb'] != int(setLimit)): 94 | tempDict = {'user':{'.tag':'email','email':str(quota['user']['email'])},'quota_gb':int(setLimit)} 95 | setQuotaList.append(tempDict) 96 | else: 97 | tempDict = {'user':{'.tag':'email','email':str(quota['user']['email'])},'quota_gb':int(setLimit)} 98 | setQuotaList.append(tempDict) 99 | dataDict['users_and_quotas'] = setQuotaList 100 | setQuotas(dataDict) 101 | 102 | 103 | # ----------- batch set quota ------------ # 104 | def setQuotas(membersDict): 105 | data = membersDict 106 | HEADERS = {} 107 | HEADERS['Authorization'] = 'Bearer ' +dfbToken 108 | r = requests.post('https://api.dropboxapi.com/2/team/member_space_limits/set_custom_quota',json = data, headers = HEADERS) 109 | resp = json.loads(r.text) 110 | for item in resp: 111 | print 'changed: ' + item['user']['email'] + ' to ' + str(item['quota_gb']) + 'GB' 112 | 113 | getDfbMembers() 114 | -------------------------------------------------------------------------------- /Users/SuspendUsers.ps1: -------------------------------------------------------------------------------- 1 | # Deprovisions users from a csv file and wipes data off all of their devices 2 | # Currently set to suspend, may be changed to delete 3 | 4 | #variables to change 5 | $filepath = Read-Host -Prompt 'Input the path to your .csv file' 6 | $logfile = Read-Host -Prompt 'Input the path to a blank .txt file for logging' 7 | $authtoken = Read-Host -Prompt 'Input your token used to deprovision' 8 | $token = "Bearer " + $authtoken 9 | 10 | # uri deletes user 11 | # $uri = "https://api.dropboxapi.com/2/team/members/remove" 12 | 13 | # uri suspends user 14 | $uri = "https://api.dropboxapi.com/2/team/members/suspend" 15 | 16 | function teamremoveMember($headerBody) 17 | { 18 | $result = Invoke-RestMethod -Uri $uri -Headers @{ "Authorization" = $token } -Body $headerBody -ContentType application/json -Method Post 19 | write-host "[-] Done " 20 | } 21 | 22 | $memberinfo = Import-Csv $filepath 23 | $count = 0 24 | foreach($line in $memberinfo) 25 | { 26 | $email = $line.email 27 | $headerBody = '{"user": {".tag": "email", "email": "' + $email + '"},"wipe_data": true}' 28 | 29 | Write-Host "[*] Suspending team member: " $email 30 | $sw = [Diagnostics.Stopwatch]::StartNew() 31 | teamremoveMember $headerBody 32 | $sw.Stop() 33 | Write-Host "[*]" $sw.Elapsed.TotalSeconds "seconds" 34 | $outstring = $count.ToString() + "," + $sw.Elapsed.TotalSeconds.ToString() 35 | $outstring | Out-File -FilePath $logfile -Append 36 | $count++ 37 | } --------------------------------------------------------------------------------