├── .gitignore
├── Files
├── MoveData.py
├── SearchTeamFiles.py
├── ListTeamFiles.py
├── TeamUsageStatistics.py
├── FileSizeQuotaAlert.py
└── QuotaAlert.py
├── Users
├── SuspendUsers.ps1
├── RemindPendingMembers.py
├── ListMembers.ps1
├── DeprovisionFromCsv.py
├── SetMemberSpaceLimits.py
├── ProvisionFromCSV.py
├── ListPendingMembersInvitedBeforeDate.py
├── GetUserList.py
└── ListMembers.py
├── AuditLog
├── GetMemberJoins.py
└── GetEvents.ps1
├── Sharing
├── ListSharedFolders-pom.xml
├── list_delete_public_dbx_links.py
├── sharingAPI.py
├── dbx_email_alerts.py
├── ListSharedLinks.py
├── ExternallySharedUsers.py
├── reports
│ └── Classes.py
└── getTeamMembersSharedLinks.py
├── README.md
├── QA Installer
├── README.md
├── Client-Event-Codes.md
└── Dropbox Enterprise Installer.ps1
├── CSV_PDF_Migration_Script.py
├── Paper
├── paper-export.py
└── convertWordToPaper.py
├── Groups
├── createGroupsFromCSV.py
├── createGroupWithAllTeamMembers.py
├── ListGroupFolderPermissions.py
└── getGroupMembers.py
├── Integrations
├── ListTeamApps.py
├── listMembersLinkedApps.py
└── ListDeviceSessions.py
└── Admin
├── wipe_external_ids.py
├── getSumOfDataInAccounts.py
└── splitTeam.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | 
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.__*
--------------------------------------------------------------------------------
/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!
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 = "')
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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)"
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.#>
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Sharing/getTeamMembersSharedLinks.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 |
10 | """
11 | A Script to iterate over all members of a team and extract a list of shared links
12 | to files and folders for each user.
13 |
14 | Tested in Python 3.6
15 |
16 | The following output files are created.
17 | * YY-MM-DD-sharedlinks.csv -> 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------