31 |
32 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Blog/Image-flipping-extension/app.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config({silent: true}); // read values from .env file
2 |
3 | const
4 | PORT = 3000, // Server port
5 | controller = require('./controller.js'),
6 | express = require('express'),
7 | app = express(),
8 | logger = require('morgan'), // Adding logging into the console
9 | hbs = require('hbs'), // Handlebars as view template
10 | session = require('express-session'); // Session management
11 |
12 | // Session management initialization
13 | // Uses local storage, for testing purposes only
14 | app.use(session({
15 | secret: process.env.SESSION_SECRET,
16 | resave: false,
17 | saveUninitialized: true
18 | }));
19 |
20 | // Temporary images will be placed in a public folder
21 | // This code does not manage cleanup
22 | app.use(express.static('public'));
23 | app.use(logger('dev'));
24 |
25 | // Handlebars set as the template engine
26 | app.set('view engine', 'hbs');
27 |
28 | // Server routing or endpoints
29 | app.get('/', controller.home); // home route
30 | app.get('/auth', controller.auth); // OAuth redirect route
31 | app.get('/dropbox_file_action',controller.fileAction); // Dropbox file actions route
32 | app.get('/save',controller.saveToDropbox); // Save flipped image to Dropbox
33 |
34 | // Start server
35 | app.listen(PORT, () => console.log(`Extensions app listening on port ${PORT}!`));
36 |
37 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color: rgba(255, 255, 255, 0.87);
7 | background-color: #242424;
8 | background-image: radial-gradient(circle at top right, rgba(121, 68, 154, 0.13), transparent),
9 | radial-gradient(circle at bottom left, rgba(41, 196, 255, 0.13), transparent);
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | padding: 0;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | background-color: transparent;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/Blog/list_all_team_contents/README.md:
--------------------------------------------------------------------------------
1 | # Team Contents Listing Example
2 |
3 | This script demonstrates how to list the contents of all of the namespaces accessible to a connected Dropbox team using the [the Dropbox API](https://www.dropbox.com/developers/documentation/http/overview) via [the Dropbox Python SDK](https://github.com/dropbox/dropbox-sdk-python).
4 |
5 | ### Getting Started
6 |
7 | This script requires Python 3.6+ and a Dropbox app key, app secret, and a refresh token for a team with the team_data.member and files.metadata.read scopes.
8 |
9 | ### Arguments
10 |
11 | Read config.ini.example for a full list of the required parameters and their descriptions.
12 |
13 | While a simple config file is used here as an example, be sure to always store sensitive values such as app secrets and refresh tokens securely.
14 |
15 | ### Running the code
16 |
17 | Example usage:
18 |
19 | Fill in your parameters in a file named config.ini and then run:
20 | ```
21 | python3 list_all_team_contents.py
22 | ```
23 |
24 | ### License
25 |
26 | Unless otherwise noted:
27 |
28 | ```
29 | Copyright (c) 2024 Dropbox, Inc.
30 |
31 | Licensed under the Apache License, Version 2.0 (the "License");
32 | you may not use this file except in compliance with the License.
33 | You may obtain a copy of the License at
34 |
35 | http://www.apache.org/licenses/LICENSE-2.0
36 |
37 | Unless required by applicable law or agreed to in writing, software
38 | distributed under the License is distributed on an "AS IS" BASIS,
39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40 | See the License for the specific language governing permissions and
41 | limitations under the License.
42 | ```
--------------------------------------------------------------------------------
/Blog/Image-flipping-extension/views/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
46 |
47 |
48 |
Flip my image
49 |
Hi {{dropbox_name}}!
50 |
This is how your flipped image looks
51 |
52 |
53 |
54 |
55 |
56 |
Original
57 |
58 |
59 |
60 |
61 |
62 |
63 |
Flipped
64 |
65 |
66 |
67 |
68 | Save to Dropbox
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Blog/performant_upload/config.ini.example:
--------------------------------------------------------------------------------
1 | [PATHS]
2 | # Local path of folder of files to upload (non-recursive)
3 | local_path = ./files_to_upload
4 | # Remote path of folder in Dropbox to upload to
5 | remote_path = /uploaded_files
6 |
7 | [AUTHORIZATION]
8 | # To upload to an account, you'll need either an access token or a refresh token with
9 | # corresponding app key/secret. The authorization must include the 'files.content.write' scope.
10 | # The access token, refresh token, and app secret should be stored securely and never made public.
11 |
12 | # Access token to use to perform the API calls
13 | access_token =
14 | # Refresh token to use to retrieve access tokens
15 | refresh_token =
16 | # App key to use to retrieve access tokens. Required when using a refresh token
17 | app_key =
18 | # App secret to use to retrieve access tokens
19 | # Required when using a refresh token not acquired using PKCE
20 | app_secret =
21 |
22 |
23 | [LIMITS]
24 | # These three values can be tuned for better (or worse) overall performance
25 | # based on the scenario and environment:
26 |
27 | # The amount of data, in bytes, to send per upload request
28 | # Must be a multiple of 4 MB
29 | # 25165824 bytes is 24 MB (24 * 1024 * 1024)
30 | chunk_size = 25165824
31 | # How many threads to use per batch of upload sessions
32 | # The maximum number of upload sessions that will be run in parallel at one time
33 | batch_thread_count = 20
34 | # How many threads to use per upload session
35 | # The maximum number of threads that will be run in parallel at one time for a single upload session
36 | concurrent_thread_count = 10
37 | # (batch_thread_count * concurrent_thread_count) is the maximum number of threads used at a time
38 | # For a large number of small files, bias batch_thread_count over concurrent_thread_count
39 | # For a small number of large files, bias concurrent_thread_count over batch_thread_count
--------------------------------------------------------------------------------
/Blog/performant_upload/README.md:
--------------------------------------------------------------------------------
1 | # Performant Upload Example
2 |
3 | This script demonstrates how to upload a large amount of data to Dropbox using [the Dropbox API](https://www.dropbox.com/developers/documentation/http/overview) via [the Dropbox Python SDK](https://github.com/dropbox/dropbox-sdk-python) in a performant manner. It aims to optimize the transfer speed by performing operations in parallel and using batch calls whenever possible, as described in [the Performance Guide](https://developers.dropbox.com/dbx-performance-guide).
4 |
5 | ### Getting Started
6 |
7 | This script requires Python 3.6+ and a Dropbox access token (or refresh token) with the ability to write files.
8 |
9 | Note that the script is configurable so that it can be tuned for different scenarios and environments. The provided example thread count settings are set high to create many threads to make use of very high bandwidth network connections, but may exhaust more limited machines/connections.
10 |
11 | ### Arguments
12 |
13 | Read config.ini.example for a full list of the required parameters and their descriptions.
14 |
15 | ### Running the code
16 |
17 | Example usage:
18 | Fill in your parameters in a file named config.ini and then run:
19 | ```
20 | python3 performant_upload.py
21 | ```
22 |
23 | ### License
24 |
25 | Unless otherwise noted:
26 |
27 | ```
28 | Copyright (c) 2022 Dropbox, Inc.
29 |
30 | Licensed under the Apache License, Version 2.0 (the "License");
31 | you may not use this file except in compliance with the License.
32 | You may obtain a copy of the License at
33 |
34 | http://www.apache.org/licenses/LICENSE-2.0
35 |
36 | Unless required by applicable law or agreed to in writing, software
37 | distributed under the License is distributed on an "AS IS" BASIS,
38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39 | See the License for the specific language governing permissions and
40 | limitations under the License.
41 | ```
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/App.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * App.jsx - Main Application Component
3 | * This file defines the main application structure, routing configuration,
4 | * and protected route implementation for the Dropbox file browser application.
5 | */
6 |
7 | import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
8 | import { AuthProvider, useAuth } from './contexts/AuthContext';
9 | import LoginPage from './components/LoginPage';
10 | import FileBrowser from './components/FileBrowser';
11 | import './App.css';
12 |
13 | /**
14 | * ProtectedRoute Component
15 | * A wrapper component that protects routes requiring authentication.
16 | * Redirects to the login page if the user is not authenticated.
17 | *
18 | * @param {Object} props - Component props
19 | * @param {React.ReactNode} props.children - Child components to render when authenticated
20 | */
21 | function ProtectedRoute({ children }) {
22 | const { isAuthenticated, loading } = useAuth();
23 |
24 | // Show loading state while authentication status is being determined
25 | if (loading) {
26 | return
Loading...
;
27 | }
28 |
29 | // Redirect to login page if not authenticated
30 | if (!isAuthenticated) {
31 | return ;
32 | }
33 |
34 | return children;
35 | }
36 |
37 | /**
38 | * AppRoutes Component
39 | * Defines the application's routing configuration.
40 | * Maps URLs to their corresponding components and handles protected routes.
41 | */
42 | function AppRoutes() {
43 | return (
44 |
45 | {/* Login page and OAuth callback handler - public route */}
46 | } />
47 | } />
48 |
49 | {/* Protected file browser route - requires authentication */}
50 |
54 |
55 |
56 | }
57 | />
58 |
59 | );
60 | }
61 |
62 | /**
63 | * App Component
64 | * The root component of the application.
65 | * Sets up routing and authentication context providers.
66 | */
67 | function App() {
68 | return (
69 |
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | export default App;
78 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/App.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | body {
17 | margin: 0;
18 | min-width: 320px;
19 | min-height: 100vh;
20 | }
21 |
22 | h1 {
23 | font-size: 3.2em;
24 | line-height: 1.1;
25 | }
26 |
27 | button {
28 | border-radius: 8px;
29 | border: 1px solid transparent;
30 | padding: 0.6em 1.2em;
31 | font-size: 1em;
32 | font-weight: 500;
33 | font-family: inherit;
34 | background-color: #1a1a1a;
35 | cursor: pointer;
36 | transition: border-color 0.25s;
37 | }
38 |
39 | button:hover {
40 | border-color: #646cff;
41 | }
42 |
43 | button:focus,
44 | button:focus-visible {
45 | outline: 4px auto -webkit-focus-ring-color;
46 | }
47 |
48 | /* Shared Button Styles */
49 | .btn {
50 | padding: 0.75rem 1.5rem;
51 | color: rgba(255, 255, 255, 0.95);
52 | border: 1px solid rgba(255, 255, 255, 0.1);
53 | border-radius: 12px;
54 | cursor: pointer;
55 | font-size: 0.95rem;
56 | transition: all 0.3s ease;
57 | display: inline-flex;
58 | align-items: center;
59 | gap: 0.5rem;
60 | backdrop-filter: blur(4px);
61 | -webkit-backdrop-filter: blur(4px);
62 | }
63 |
64 | .btn:hover {
65 | background: rgba(0, 81, 212, 0.9);
66 | transform: translateY(-2px);
67 | box-shadow: 0 4px 12px rgba(0, 97, 254, 0.2);
68 | }
69 |
70 | .btn:active {
71 | transform: translateY(0);
72 | }
73 |
74 | .btn:disabled {
75 | background: rgba(204, 204, 204, 0.3);
76 | cursor: not-allowed;
77 | transform: none;
78 | box-shadow: none;
79 | }
80 |
81 | .btn-secondary {
82 | background: rgba(51, 51, 51, 0.5);
83 | }
84 |
85 | .btn-secondary:hover {
86 | background: rgba(58, 58, 58, 0.7);
87 | border-color: rgba(255, 255, 255, 0.2);
88 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
89 | }
90 |
91 | .btn-success {
92 | background: rgba(46, 164, 79, 0.8);
93 | }
94 |
95 | .btn-success:hover {
96 | background: rgba(44, 151, 75, 0.9);
97 | box-shadow: 0 4px 12px rgba(46, 164, 79, 0.2);
98 | }
99 |
100 | .btn-icon {
101 | padding: 0.5rem;
102 | width: 36px;
103 | height: 36px;
104 | justify-content: center;
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/components/LoginPage.css:
--------------------------------------------------------------------------------
1 | .login-container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | min-height: 100vh;
7 | padding: 2rem;
8 | background: rgba(42, 42, 42, 0.7);
9 | backdrop-filter: blur(10px);
10 | -webkit-backdrop-filter: blur(10px);
11 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
12 | border: 1px solid rgba(255, 255, 255, 0.1);
13 | }
14 |
15 | h1 {
16 | color: rgba(255, 255, 255, 0.95);
17 | margin-bottom: 2rem;
18 | text-align: center;
19 | text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
20 | font-size: 2.5rem;
21 | font-weight: 600;
22 | }
23 |
24 | .auth-options {
25 | margin-bottom: 2rem;
26 | padding: 1.5rem;
27 | background: rgba(51, 51, 51, 0.5);
28 | border-radius: 12px;
29 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
30 | border: 1px solid rgba(255, 255, 255, 0.1);
31 | backdrop-filter: blur(4px);
32 | -webkit-backdrop-filter: blur(4px);
33 | width: 100%;
34 | max-width: 600px;
35 | transition: all 0.3s ease;
36 | }
37 |
38 | .auth-options:hover {
39 | background: rgba(56, 56, 56, 0.6);
40 | transform: translateY(-2px);
41 | }
42 |
43 | .offline-access-option {
44 | display: flex;
45 | align-items: center;
46 | gap: 0.75rem;
47 | font-size: 1rem;
48 | color: rgba(255, 255, 255, 0.9);
49 | cursor: pointer;
50 | justify-content: center;
51 | }
52 |
53 | .offline-access-option input[type="checkbox"] {
54 | width: 18px;
55 | height: 18px;
56 | cursor: pointer;
57 | background: rgba(51, 51, 51, 0.5);
58 | border: 1px solid rgba(255, 255, 255, 0.2);
59 | border-radius: 4px;
60 | accent-color: #0061fe;
61 | }
62 |
63 | .login-buttons {
64 | display: flex;
65 | flex-direction: row;
66 | gap: 1.5rem;
67 | width: 100%;
68 | max-width: 600px;
69 | justify-content: center;
70 | }
71 |
72 | .login-buttons .btn {
73 | flex: 1;
74 | max-width: 280px;
75 | padding: 1rem;
76 | font-size: 1.1rem;
77 | background: rgba(0, 97, 254, 0.9);
78 | color: white;
79 | border: none;
80 | border-radius: 12px;
81 | cursor: pointer;
82 | transition: all 0.3s ease;
83 | backdrop-filter: blur(4px);
84 | -webkit-backdrop-filter: blur(4px);
85 | box-shadow: 0 4px 12px rgba(0, 97, 254, 0.2);
86 | }
87 |
88 | .login-buttons .btn:hover {
89 | background: rgba(0, 97, 254, 1);
90 | transform: translateY(-2px);
91 | box-shadow: 0 6px 16px rgba(0, 97, 254, 0.3);
92 | }
93 |
94 | .login-buttons .btn:active {
95 | transform: translateY(0);
96 | box-shadow: 0 2px 8px rgba(0, 97, 254, 0.2);
97 | }
98 |
99 | /* Responsive Design */
100 | @media (max-width: 768px) {
101 | .login-container {
102 | padding: 1rem;
103 | }
104 |
105 | h1 {
106 | font-size: 2rem;
107 | }
108 |
109 | .login-buttons {
110 | flex-direction: column;
111 | gap: 1rem;
112 | }
113 |
114 | .login-buttons .btn {
115 | max-width: 100%;
116 | }
117 | }
--------------------------------------------------------------------------------
/Blog/Convert-activity-log-to-CEF-events/README.MD:
--------------------------------------------------------------------------------
1 | # Convert Dropbox activity log to CEF events
2 |
3 | By Tahsin Islam
4 | April, 2020
5 |
6 | This script can be used to transform Dropbox activity log events retrieved from the [team log endpoint](https://www.dropbox.com/developers/documentation/http/teams#team_log-get_events) to the [CEF standard](https://community.microfocus.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Implementation-Standard/ta-p/1645557?attachment-id=68077) developed by MicroFocus ArcSight.
7 |
8 | ### Getting Started
9 |
10 | To use the script you will need to have a copy of Python 3.0+ and Dropbox Business access token with either the **"Team Auditing"** or **"Team Member File Access"** permission.
11 |
12 | To get started with running the script you will need to paste your access token in the parserSettings.ini file as indicated.
13 |
14 | With your access token put in you can then proceed to run and execute the script simply by calling ```python cefParser.py``` and associated arguments. You can see the full list of arguments possible in the below section.
15 |
16 |
17 | ### Run Examples
18 |
19 | **Objective**: Generate a CSV of CEF events for all single sign on events
20 | ```
21 | python cefParser.py --output --category sso
22 | ```
23 |
24 | In this example passing in the argument --output will generate a CSV file of the events pulled.```--category``` or ```-c``` designates you want to filter on an event category and sso (single sign on) is the category filter.
25 |
26 | **Objective**: Send all category of events to designated syslog server
27 | ```
28 | python cefParser.py --host 121.121.01.10 --port 1234
29 | ```
30 |
31 | Here you’re sending all events to the host and port you’ve designated, by using the ```--host``` and ```--port``` arguments.
32 |
33 | **Objective**: Send all CEF events from 2018 up to 2019 to designated syslog server
34 | ```
35 | python cefParser.py --host 121.121.01.10 --port 1234 --start_time 2018-01-01T00:00:00Z --end_time 2019-01-01T00:00:00Z
36 | ```
37 |
38 | ### Arguments
39 | ```'-c','--category'```: The category of events you want to pull from audit log. If no category is passed all events are pulled.
40 | ```categories: 'apps', 'comments', 'devices', 'domains', 'file\_operations', 'file\_requests', 'groups', 'logins', 'members', 'paper', 'passwords', 'reports', 'sharing', 'showcase', 'sso', 'team\_folders', 'team\_policies', 'team\_profile', 'tfa', 'trusted\_teams' ```
41 | ```'-l','--limit'```: The max amount of events to pull from the audit log at one time, default is 1000.
42 | ```'--output'```: Passing this will print a csv file of your query to your current directory named dropbox\_cefevents.csv.
43 | ```'--host'```: The host address for the designated syslog destination to send events.
44 | ```'--port'```: The port address for the designated syslog destination to send events.
45 | ```'-st','--start_time'```: The starting time from when to include events (inclusive), format=```%%Y-%%m-%%dT%%H:%%M:%%SZ```
46 | ```'-et','--end_time'``` : The end time from when to include events (exclusive), format=```%%Y-%%m-%%dT%%H:%%M:%%SZ```
47 | ```'-csr', '--cursor'```: Pass event cursor from activity log, to pull new events from last query.
48 |
49 | ### Licensing
50 | The script within this folder is covered by the Apache License as described in LICENSE.txt.
51 |
52 | ##### Please carefully note:
53 |
54 | > "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."
55 |
--------------------------------------------------------------------------------
/Blog/list_all_team_contents/list_all_team_contents.py:
--------------------------------------------------------------------------------
1 | """Example code for listing all entries in all namespaces accessible to a team."""
2 |
3 | import configparser
4 |
5 | import dropbox
6 |
7 |
8 | def list_all_contents():
9 | """Lists the contents of each namespace accessible to a team."""
10 |
11 | config = configparser.ConfigParser()
12 | config.read("config.ini")
13 |
14 | # We'll first get a client for interacting with the team to list the namespaces
15 | with dropbox.DropboxTeam(
16 | app_key=config.get("DEFAULT", "app_key"),
17 | app_secret=config.get("DEFAULT", "app_secret"),
18 | oauth2_refresh_token=config.get("DEFAULT", "team_refresh_token"),
19 | ) as dbx_team:
20 | # The functionality for listing the namespaces may return duplicates,
21 | # so we'll use a set here to ensure uniqueness
22 | namespace_ids = set()
23 |
24 | def handle_namespaces_result(result):
25 | """Processes each page of namespaces."""
26 | for namespace in result.namespaces:
27 | namespace_ids.add(namespace.namespace_id)
28 |
29 | namespaces_result = dbx_team.team_namespaces_list()
30 | handle_namespaces_result(namespaces_result)
31 |
32 | # The interface for retrieving the list of namespaces is paginated,
33 | # so we need to make sure we retrieve and process every page of results
34 | while namespaces_result.has_more:
35 | namespaces_result = dbx_team.team_namespaces_list_continue(
36 | cursor=namespaces_result.cursor
37 | )
38 | handle_namespaces_result(namespaces_result)
39 |
40 | namespace_ids = sorted(list(namespace_ids), key=int)
41 | print(f"Received list of {len(namespace_ids)} namespaces for team.")
42 |
43 | # Now that we've retrieved all the namespaces,
44 | # we'll get an admin client to access those namespaces
45 | with dbx_team.as_admin(
46 | team_member_id=config.get("DEFAULT", "team_admin_member_id")
47 | ) as dbx_admin:
48 |
49 | def handle_listing_result(result):
50 | """Processes each page of file/folder entries.
51 | Refer to the documentation for information on how to use these entries:
52 | https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder"""
53 | for entry in result.entries:
54 | print(
55 | f"\tReceived entry of type {type(entry)} "
56 | f"at path: {entry.path_lower}"
57 | )
58 |
59 | for namespace_id in namespace_ids:
60 | print(f"Listing namespace with ID: {namespace_id}")
61 |
62 | # For each namespace, we can make a client rooted to that namespace
63 | with dbx_admin.with_path_root(
64 | dropbox.common.PathRoot.namespace_id(namespace_id)
65 | ) as dbx_admin_with_ns:
66 |
67 | listing_result = dbx_admin_with_ns.files_list_folder(
68 | # Because this client is rooted to this namespace,
69 | # we use the empty string `""` as the `path` to
70 | # list the root folder of this namespace
71 | path="",
72 | # Request a recursive listing to get nested entries as well
73 | recursive=True,
74 | # Skip mounted folders because they'll be in the namespace list
75 | include_mounted_folders=False
76 | )
77 | handle_listing_result(listing_result)
78 |
79 | # Just like with getting the list of namespaces,
80 | # the interface for getting the list of contents of a folder is paginated,
81 | # so we need to make sure we retrieve and process every page of results
82 | while listing_result.has_more:
83 | listing_result = dbx_admin_with_ns.files_list_folder_continue(
84 | cursor=listing_result.cursor
85 | )
86 | handle_listing_result(listing_result)
87 |
88 |
89 | if __name__ == "__main__":
90 | list_all_contents()
91 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/components/ShareForm.css:
--------------------------------------------------------------------------------
1 | .share-form-overlay {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | background: rgba(0, 0, 0, 0.7);
8 | backdrop-filter: blur(4px);
9 | -webkit-backdrop-filter: blur(4px);
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | z-index: 1000;
14 | }
15 |
16 | .share-form {
17 | background: rgba(42, 42, 42, 0.95);
18 | border-radius: 16px;
19 | padding: 2rem;
20 | width: 100%;
21 | max-width: 500px;
22 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
23 | border: 1px solid rgba(255, 255, 255, 0.1);
24 | position: relative;
25 | }
26 |
27 | .close-button {
28 | position: absolute;
29 | top: 1rem;
30 | right: 1rem;
31 | background: transparent;
32 | border: none;
33 | color: rgba(255, 255, 255, 0.7);
34 | font-size: 1.5rem;
35 | cursor: pointer;
36 | padding: 0.5rem;
37 | line-height: 1;
38 | }
39 |
40 | .close-button:hover {
41 | color: #fff;
42 | }
43 |
44 | .share-form h2 {
45 | color: rgba(255, 255, 255, 0.95);
46 | margin: 0 0 1.5rem;
47 | font-size: 1.5rem;
48 | font-weight: 600;
49 | }
50 |
51 | .form-group {
52 | margin-bottom: 1.5rem;
53 | }
54 |
55 | .form-group label {
56 | display: block;
57 | color: rgba(255, 255, 255, 0.8);
58 | margin-bottom: 0.5rem;
59 | font-size: 0.95rem;
60 | }
61 |
62 | .form-group select {
63 | width: 100%;
64 | padding: 0.75rem;
65 | background: rgba(51, 51, 51, 0.7);
66 | border: 1px solid rgba(255, 255, 255, 0.1);
67 | border-radius: 8px;
68 | color: rgba(255, 255, 255, 0.9);
69 | font-size: 0.95rem;
70 | transition: all 0.3s ease;
71 | }
72 |
73 | .form-group select:hover:not(:disabled) {
74 | border-color: rgba(0, 97, 254, 0.5);
75 | background: rgba(56, 56, 56, 0.8);
76 | }
77 |
78 | .form-group select:focus {
79 | outline: none;
80 | border-color: #0061fe;
81 | box-shadow: 0 0 0 2px rgba(0, 97, 254, 0.2);
82 | }
83 |
84 | .checkbox-label {
85 | display: flex !important;
86 | align-items: center;
87 | gap: 0.75rem;
88 | cursor: pointer;
89 | }
90 |
91 | .checkbox-label input[type="checkbox"] {
92 | width: 18px;
93 | height: 18px;
94 | accent-color: #0061fe;
95 | cursor: pointer;
96 | }
97 |
98 | .password-input {
99 | margin-top: 0.75rem;
100 | width: 100%;
101 | box-sizing: border-box;
102 | padding: 0.75rem;
103 | background: rgba(51, 51, 51, 0.7);
104 | border: 1px solid rgba(255, 255, 255, 0.1);
105 | border-radius: 8px;
106 | color: rgba(255, 255, 255, 0.9);
107 | font-size: 0.95rem;
108 | }
109 |
110 | .custom-date-input {
111 | margin-top: 0.75rem;
112 | position: relative;
113 | }
114 |
115 | .custom-date-input svg {
116 | position: absolute;
117 | left: 0.75rem;
118 | top: 50%;
119 | transform: translateY(-50%);
120 | color: rgba(255, 255, 255, 0.5);
121 | }
122 |
123 | .custom-date-input input {
124 | width: 100%;
125 | box-sizing: border-box;
126 | padding: 0.75rem 0.75rem 0.75rem 2.5rem;
127 | background: rgba(51, 51, 51, 0.7);
128 | border: 1px solid rgba(255, 255, 255, 0.1);
129 | border-radius: 8px;
130 | color: rgba(255, 255, 255, 0.9);
131 | font-size: 0.95rem;
132 | }
133 |
134 | .shared-link-container {
135 | margin: 1.5rem 0;
136 | display: flex;
137 | gap: 0.75rem;
138 | }
139 |
140 | .shared-link-input {
141 | flex: 1;
142 | padding: 0.75rem;
143 | background: rgba(51, 51, 51, 0.7);
144 | border: 1px solid rgba(255, 255, 255, 0.1);
145 | border-radius: 8px;
146 | color: rgba(255, 255, 255, 0.9);
147 | font-size: 0.95rem;
148 | }
149 |
150 | .error-message {
151 | background: rgba(255, 59, 48, 0.1);
152 | border: 1px solid rgba(255, 59, 48, 0.2);
153 | color: rgb(255, 89, 89);
154 | padding: 0.75rem;
155 | border-radius: 8px;
156 | margin-bottom: 1.5rem;
157 | font-size: 0.95rem;
158 | }
159 |
160 | .form-actions {
161 | display: flex;
162 | gap: 0.75rem;
163 | margin-top: 2rem;
164 | }
165 |
166 | .form-actions button {
167 | padding: 0.75rem 1.5rem;
168 | border-radius: 8px;
169 | font-size: 0.95rem;
170 | font-weight: 500;
171 | display: flex;
172 | align-items: center;
173 | gap: 0.5rem;
174 | transition: all 0.3s ease;
175 | }
176 |
177 | .form-actions button:disabled {
178 | opacity: 0.6;
179 | cursor: not-allowed;
180 | }
181 |
182 | .btn-primary {
183 | background: #0061fe;
184 | color: white;
185 | border: none;
186 | flex: 1;
187 | }
188 |
189 | .btn-primary:hover:not(:disabled) {
190 | background: #0056e4;
191 | transform: translateY(-1px);
192 | }
193 |
194 | .btn-secondary {
195 | background: rgba(255, 255, 255, 0.1);
196 | color: white;
197 | border: 1px solid rgba(255, 255, 255, 0.1);
198 | }
199 |
200 | .btn-secondary:hover:not(:disabled) {
201 | background: rgba(255, 255, 255, 0.15);
202 | transform: translateY(-1px);
203 | }
204 |
205 | .btn-danger {
206 | background: rgba(219, 14, 3, 0.719);
207 | border: 1px solid rgba(255, 59, 48, 0.2);
208 | }
209 |
210 | .btn-danger:hover:not(:disabled) {
211 | background: rgba(255, 59, 48, 0.2);
212 | transform: translateY(-1px);
213 | }
214 |
215 | .access-level-info {
216 | font-size: 0.85em;
217 | color: #666;
218 | margin-top: 4px;
219 | font-style: italic;
220 | }
221 |
222 | /* Responsive Design */
223 | @media (max-width: 576px) {
224 | .share-form {
225 | margin: 1rem;
226 | padding: 1.5rem;
227 | }
228 |
229 | .form-actions {
230 | flex-direction: column;
231 | }
232 |
233 | .form-actions button {
234 | width: 100%;
235 | }
236 |
237 | .shared-link-container {
238 | flex-direction: column;
239 | }
240 | }
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/components/LoginPage.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * LoginPage.jsx - Authentication Entry Point and Callback Handler
3 | * This component provides the initial login interface for both individual and team-based
4 | * Dropbox authentication, and handles the OAuth callback flow.
5 | */
6 |
7 | import React, { useState, useEffect, useRef } from 'react';
8 | import { useNavigate, useSearchParams } from 'react-router-dom';
9 | import { getAuthUrl, getTokensFromCode } from '../utils/dropboxClient';
10 | import { DROPBOX_CONFIG, useAuth } from '../contexts/AuthContext';
11 | import './LoginPage.css';
12 |
13 | /**
14 | * LoginPage Component
15 | * Provides UI for initiating Dropbox authentication flow and handles OAuth callback.
16 | * Supports both individual user and team authentication.
17 | */
18 | export default function LoginPage() {
19 | const navigate = useNavigate();
20 | const { handleLogin } = useAuth();
21 | const [searchParams] = useSearchParams();
22 |
23 | // State management
24 | const [isLoading, setIsLoading] = useState(false);
25 | const [error, setError] = useState(null);
26 | const [offlineAccess, setOfflineAccess] = useState(false);
27 |
28 | // Prevents multiple token exchange attempts
29 | const hasExchangedCode = useRef(false);
30 |
31 | /**
32 | * Initiates authentication flow by redirecting to Dropbox OAuth page
33 | * @param {boolean} teamAuth - Whether to request team-level permissions
34 | */
35 | const handleLoginRedirect = async (teamAuth) => {
36 | try {
37 | setIsLoading(true);
38 | // Store offline access preference in session storage before redirect
39 | sessionStorage.setItem('requestedOfflineAccess', offlineAccess.toString());
40 |
41 | const authUrl = await getAuthUrl(
42 | DROPBOX_CONFIG.clientId,
43 | DROPBOX_CONFIG.redirectUri,
44 | offlineAccess,
45 | teamAuth
46 | );
47 | window.location.href = authUrl;
48 | } catch (error) {
49 | console.error(`Failed to get ${teamAuth ? 'team' : 'user'} auth URL:`, error);
50 | setError('Failed to start login process. Please try again.');
51 | } finally {
52 | setIsLoading(false);
53 | }
54 | };
55 |
56 | /**
57 | * Processes the OAuth callback parameters and exchanges the code for tokens
58 | * Handles various error cases and successful authentication
59 | */
60 | useEffect(() => {
61 | const handleCallback = async () => {
62 | const code = searchParams.get('code');
63 | // If no code is present, this is not a callback - show login page
64 | if (!code) {
65 | return;
66 | }
67 |
68 | // Prevent multiple token exchanges
69 | if (hasExchangedCode.current) {
70 | return;
71 | }
72 |
73 | setIsLoading(true);
74 |
75 | const error = searchParams.get('error');
76 | const errorDescription = searchParams.get('error_description');
77 |
78 | // Get offline access preference from session storage
79 | const offlineAccess = sessionStorage.getItem('requestedOfflineAccess') === 'true';
80 | // Clean up session storage
81 | sessionStorage.removeItem('requestedOfflineAccess');
82 |
83 | // Handle OAuth errors
84 | if (error || errorDescription) {
85 | const errorMessage = errorDescription || error;
86 | console.error('OAuth error:', { error, errorDescription });
87 | setError(`Authentication failed: ${errorMessage}`);
88 | setIsLoading(false);
89 | return;
90 | }
91 |
92 | try {
93 | // Mark code exchange as in progress
94 | hasExchangedCode.current = true;
95 | // Exchange code for tokens
96 | const tokens = await getTokensFromCode(
97 | DROPBOX_CONFIG.clientId,
98 | code,
99 | DROPBOX_CONFIG.redirectUri
100 | );
101 |
102 | // Complete authentication and redirect to file browser
103 | handleLogin(tokens, offlineAccess);
104 | navigate('/browser');
105 | } catch (error) {
106 | console.error('Token exchange error:', error);
107 | setError(`Failed to complete authentication: ${error.message}`);
108 | } finally {
109 | setIsLoading(false);
110 | }
111 | };
112 |
113 | handleCallback();
114 | }, [searchParams, handleLogin, navigate]);
115 |
116 | // If there's an error, show error state
117 | if (error) {
118 | return (
119 |
120 |
Authentication Error
121 |
{error}
122 |
125 |
126 | );
127 | }
128 |
129 | // If we're processing a callback, show loading state
130 | if (isLoading && searchParams.get('code')) {
131 | return (
132 |
133 |
Completing authentication...
134 |
Please wait while we complete the authentication process.
175 | );
176 | }
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/README.md:
--------------------------------------------------------------------------------
1 | # Dropbox File Browser
2 |
3 | A client-side web application built with the Dropbox JavaScript SDK and React (using Vite) for browsing, viewing, and managing files in a Dropbox account. It demonstrates how to use the Dropbox API with OAuth 2.0, supporting both individual and team accounts.
4 |
5 | ## Features
6 |
7 | ### Core Features
8 | - **Browse and Navigate**: Traverse through your Dropbox folders and files.
9 | - **Download Files**: Download files directly from your Dropbox.
10 | - **Upload Files**: Upload files to the current Dropbox folder.
11 | - **Create Shared Links**: Create and manage shared links for your files and folders.
12 | - **Team Support**: For Dropbox Business accounts, switch between team members to view their files. (Team admin only)
13 | - **Namespace Toggle**: Switch between personal and team spaces when you're a member of a Dropbox team.
14 | - **Breadcrumb Navigation**: Easily navigate the folder hierarchy.
15 | - **File Details**: View file size and last modified date.
16 | - **Real-time Updates**: Automatically sync file changes using long-polling.
17 |
18 | ### Technical Features
19 | - **Secure Authentication**: Uses OAuth 2.0 with PKCE flow for secure client-side authentication.
20 | - **Offline Access**: Optional long-term access via refresh tokens to stay logged in.
21 | - **Team Space Management**: Properly handles path roots for accessing team spaces.
22 | - **Responsive UI**: A clean interface that works on different screen sizes.
23 |
24 | ## Project Structure
25 |
26 | ```
27 | file_browser/
28 | ├── .env # Environment variables (you need to create this)
29 | ├── .gitignore # Git ignore rules
30 | ├── index.html # Main HTML entry point
31 | ├── package.json # Project dependencies and scripts
32 | ├── vite.config.js # Vite configuration
33 | └── src/
34 | ├── components/ # React components
35 | │ ├── FileBrowser.jsx
36 | │ ├── LoginPage.jsx
37 | │ └── ...
38 | ├── contexts/ # React contexts
39 | │ └── AuthContext.jsx
40 | ├── utils/ # Utility functions
41 | │ └── dropboxClient.js
42 | ├── App.jsx # Main application component
43 | └── main.jsx # Application entry point
44 | ```
45 |
46 | ## Setup
47 |
48 | ### 1. Dropbox API Configuration
49 |
50 | 1. Go to the [Dropbox App Console](https://www.dropbox.com/developers/apps) and click "Create app".
51 | 2. Choose the following settings:
52 | * **API**: "Scoped access"
53 | * **Access type**: "Full Dropbox"
54 | * **Name**: Choose a unique name for your app.
55 |
56 | 3. Configure your new Dropbox app:
57 | * In the **Settings** tab:
58 | * Note your "App key". This will be your `VITE_DROPBOX_APP_KEY`.
59 | * Add `http://localhost:5173/oauth-callback` to "Redirect URIs".
60 | * In the **Permissions** tab, enable these scopes:
61 | * `account_info.read` (For accessing user account information)
62 | * `files.metadata.read` (For listing files and folders)
63 | * `files.content.read` (For downloading files)
64 | * `files.content.write` (For uploading files)
65 | * `sharing.read` (For reading shared link metadata)
66 | * `sharing.write` (For creating and modifying shared links)
67 | * **For Team features, also add:**
68 | * `members.read` (For listing team members)
69 | * `team_data.member` (For switching between team members)
70 | * `team_info.read` (For accessing team information)
71 |
72 | ### 2. Local Environment Setup
73 |
74 | 1. Clone the repository and navigate to the project directory:
75 | ```sh
76 | git clone
77 | cd JavaScript/file_browser
78 | ```
79 |
80 | 2. Install dependencies:
81 | ```sh
82 | npm install
83 | ```
84 |
85 | 3. Set up environment variables by creating a `.env` file in the `file_browser` directory:
86 | ```env
87 | VITE_DROPBOX_APP_KEY="YOUR_APP_KEY_FROM_DROPBOX_CONSOLE"
88 | VITE_DROPBOX_REDIRECT_URI="http://localhost:5173/oauth-callback"
89 | ```
90 | Replace `YOUR_APP_KEY_FROM_DROPBOX_CONSOLE` with your actual Dropbox App key.
91 |
92 | 4. Run the application:
93 | ```sh
94 | npm run dev
95 | ```
96 | The application will be available at `http://localhost:5173`.
97 |
98 | ## Usage Notes
99 |
100 | ### For personal use:
101 | - Log in with your Dropbox account
102 | - The app will request the necessary permissions
103 | - You can access your personal and team files and folders
104 |
105 | ### For team features:
106 | - You need admin access to a Dropbox Business team
107 | - Log in with your team admin account
108 | - You can switch between team members
109 |
110 | ### Access Token Options:
111 | - **Short-term access (default)**: Uses short-lived access tokens; re-authentication required periodically.
112 | - **Long-term access**: Uses short-lived access tokens with refresh tokens; re-authentication not required. Check "Request long-term access" on login.
113 |
114 | ## Sharing Features
115 |
116 | ### Create shared links for any file or folder
117 | ### Configure sharing settings:
118 | #### Audience Control:
119 | - Public access (anyone with the link)
120 | - Team-only access (only team members)
121 | - No additional access (link doesn't grant additional permissions beyond what users already have)
122 |
123 | #### Security Options:
124 | - Password protection
125 | - Custom expiration dates
126 | - Download permissions
127 |
128 | ### Manage existing shared links:
129 | - View current settings
130 | - Update settings
131 | - Revoke access
132 |
133 | **Note**: In current implementation shared links are created with viewer-only access. This means recipients can view but not edit the shared content.
134 |
135 | ## Prerequisites
136 |
137 | **Node version: 22.12 or above**: Please make sure you have Node version 22.12 or above in order to avoid the error `TypeError: crypto.hash is not a function.`
138 |
139 | ## Dependencies
140 |
141 | This project relies on the following key packages:
142 | - **React**: A JavaScript library for building user interfaces.
143 | - **React Router**: For client-side routing.
144 | - **Dropbox SDK**: For interacting with the Dropbox API.
145 | - **Vite**: As the frontend build tool and development server.
146 |
--------------------------------------------------------------------------------
/Sample Apps/Python/file_browser/README.md:
--------------------------------------------------------------------------------
1 | # Dropbox File Browser
2 |
3 | A web-based file browser application built with Flask and the Dropbox Python SDK. This application demonstrates the usage of Dropbox API for both individual users and team accounts.
4 |
5 | ## Features
6 |
7 | ### Core Features
8 | - Browse and navigate through Dropbox folders
9 | - Download files directly from Dropbox
10 | - Upload a file with support for large file sizes (chunked upload)
11 | - Create and manage shared links for files and folders
12 | - Customize sharing settings including:
13 | - Access level (public/team/no additional access)
14 | - Password protection
15 | - Link expiration
16 | - Download permissions
17 | - File size and modification date display
18 | - Breadcrumb navigation for easy folder traversal
19 | - Toggle between personal and team spaces
20 | - Switch between team members' accounts (team admin only)
21 |
22 | ### Technical Features
23 | - OAuth 2 authentication with Dropbox API
24 | - Support for both short-term and long-term access
25 | - Efficient file metadata handling
26 | - Proper path root management for team spaces
27 | - Error handling and session management
28 | - Responsive web interface
29 |
30 | ## Project Structure
31 |
32 | ```
33 | file_browser/
34 | ├── app.py # Main Flask application and Dropbox API integration
35 | ├── requirements.txt # Python dependencies
36 | ├── .env # Environment variables (create this file)
37 | ├── templates/ # HTML templates
38 | │ ├── index.html # Landing page with OAuth login options
39 | │ └── file_browser.html # Main file browser interface
40 | └── .gitignore # Git ignore rules
41 | ```
42 |
43 | ### Key Components:
44 | - `app.py`: Contains all the route handlers and Dropbox API integration logic
45 | - OAuth 2 authentication flow
46 | - File/folder browsing logic
47 | - Team space management
48 | - Download functionality
49 | - File upload functionality
50 | - Shared link creation and management
51 | - Team data transport API call limit handling
52 | - `templates/`: HTML templates with a clean, responsive design
53 | - `index.html`: Simple landing page with user/team login options
54 | - `file_browser.html`: Main interface with file browsing and team management
55 | - `requirements.txt`: Lists required Python packages:
56 | - Flask for web framework
57 | - Dropbox SDK for API integration
58 | - python-dotenv for environment management
59 |
60 | ## Setup
61 |
62 | ### 1. Dropbox API Configuration
63 |
64 | 1. Go to the [Dropbox App Console](https://www.dropbox.com/developers/apps)
65 | 2. Click "Create app"
66 | 3. Choose the following settings:
67 | - Choose an API: "Scoped access"
68 | - Choose the type of access: "Full Dropbox"
69 | - Name your app: Choose a unique name
70 |
71 | 4. Configure your new Dropbox app:
72 | - In the Settings tab:
73 | - Note your "App key" and "App secret"
74 | - Add `http://localhost:5000/oauth/callback` to "Redirect URIs"
75 |
76 |
77 | - In the Permissions tab, enable these permissions:
78 | ```
79 | account_info.read (For accessing user account information)
80 | files.metadata.read (For listing files and folders)
81 | files.content.read (For downloading files)
82 | files.content.write (For uploading files)
83 | sharing.write (For creating and modifying shared links)
84 | sharing.read (For reading shared link metadata)
85 |
86 | # Additional scopes for team features:
87 | team_info.read (For accessing team information)
88 | team_data.member (For switching between team members)
89 | members.read (For listing team members)
90 | ```
91 |
92 | > **Important Note**: The user scopes provide access to both individual and team space content. If you're a member of a Dropbox team, you can access your team's shared files and folders with just the basic scopes (`account_info.read`, `files.metadata.read`, and `files.content.read`). The additional team scopes are only needed for admin tasks like switching between team members' accounts.
93 |
94 | ### 2. Local Environment Setup
95 |
96 | 1. Clone the repository and create a virtual environment:
97 | ```bash
98 | python -m venv venv
99 | source venv/bin/activate # On Unix/macOS
100 | # OR
101 | .\venv\Scripts\activate # On Windows
102 | ```
103 |
104 | 2. Install dependencies:
105 | ```bash
106 | pip install -r requirements.txt
107 | ```
108 |
109 | 3. Set up environment variables using one of these methods:
110 |
111 | Option A: Using a .env file (recommended for development)
112 | ```bash
113 | # Create a .env file in the project root:
114 | DROPBOX_APP_KEY=your_app_key_from_dropbox_console
115 | DROPBOX_APP_SECRET=your_app_secret_from_dropbox_console
116 | DROPBOX_REDIRECT_URI=http://localhost:5000/oauth/callback
117 | ```
118 |
119 | Option B: Setting environment variables directly
120 | ```bash
121 | # Unix/macOS:
122 | export DROPBOX_APP_KEY=your_app_key_from_dropbox_console
123 | export DROPBOX_APP_SECRET=your_app_secret_from_dropbox_console
124 | export DROPBOX_REDIRECT_URI=http://localhost:5000/oauth/callback
125 |
126 | # Windows Command Prompt:
127 | set DROPBOX_APP_KEY=your_app_key_from_dropbox_console
128 | set DROPBOX_APP_SECRET=your_app_secret_from_dropbox_console
129 | set DROPBOX_REDIRECT_URI=http://localhost:5000/oauth/callback
130 |
131 | # Windows PowerShell:
132 | $env:DROPBOX_APP_KEY="your_app_key_from_dropbox_console"
133 | $env:DROPBOX_APP_SECRET="your_app_secret_from_dropbox_console"
134 | $env:DROPBOX_REDIRECT_URI="http://localhost:5000/oauth/callback"
135 | ```
136 |
137 | 4. Run the application:
138 | ```bash
139 | python app.py
140 | ```
141 |
142 | The application will be available at `http://localhost:5000`
143 |
144 | ### 3. Usage Notes
145 |
146 | - For personal use:
147 | - Log in with your Dropbox account
148 | - The app will request the necessary permissions
149 | - You can access your personal and team files and folders
150 |
151 | - For team features:
152 | - You need admin access to a Dropbox Business team
153 | - Log in with your team admin account
154 | - You can switch between team members
155 |
156 | - Access Token Options:
157 | - Short-term access (default): Uses short-lived access tokens; re-authenticate required periodically.
158 | - Long-term access: Uses short-lived access tokens with refresh tokens; re-authentication not required. Check "Request long-term access" on login.
159 |
160 | ### 4. Sharing Features
161 |
162 | - Create shared links for any file or folder
163 | - Configure sharing settings:
164 | - Audience Control:
165 | - Public access (anyone with the link)
166 | - Team-only access (only team members)
167 | - No additional access (link doesn't grant additional permissions beyond what users already have)
168 | - Security Options:
169 | - Password protection
170 | - Custom expiration dates
171 | - Download permissions
172 | - Manage existing shared links:
173 | - View current settings
174 | - Update settings
175 | - Revoke access
176 |
177 | > **Note**: In current implementation shared links are created with viewer-only access. This means recipients can view but not edit the shared content.
178 |
179 | ## Dependencies
180 |
181 | - Flask
182 | - Dropbox SDK
183 | - python-dotenv
184 |
185 |
--------------------------------------------------------------------------------
/Blog/Convert-activity-log-to-CEF-events/cefParser.py:
--------------------------------------------------------------------------------
1 | import requests, json, argparse, csv, datetime, socket, configparser
2 |
3 | #Get audit log events
4 | def getEvents(eventCategory, cursor):
5 | print("Pulling events...")
6 | headers = {"Authorization": "Bearer "+defaultConfig["dfbToken"], "Content-type": "application/json"}
7 | if cursor is not None:
8 | endpoint = "https://api.dropbox.com/2/team_log/get_events/continue"
9 | data = {"cursor": cursor}
10 | else:
11 | data = {"limit": args.limit}
12 | endpoint = "https://api.dropbox.com/2/team_log/get_events"
13 | if eventCategory:
14 | data['category'] = eventCategory
15 | if args.end_time and args.start_time:
16 | data['time'] = {'start_time': args.start_time, 'end_time': args.end_time}
17 | elif args.end_time:
18 | data['time'] = {'end_time': args.end_time}
19 | elif args.start_time:
20 | data['time'] = {'start_time': args.start_time}
21 | response = requests.post(url=endpoint, headers=headers, data=json.dumps(data))
22 |
23 | try:
24 | response.raise_for_status()
25 | rbody = response.json()
26 | events = rbody['events']
27 | if rbody["has_more"]:
28 | events = events + getEvents(eventCategory, cursor=rbody["cursor"])[0]
29 | return (events, rbody["cursor"])
30 | except requests.exceptions.HTTPError as e:
31 | #Print details of non 200 response. Most likely bad token or event category
32 | print (e)
33 |
34 | #Escape string values for CEF
35 | def cleanData(eventData):
36 | data = str(eventData).strip()
37 | if len(data) > 0:
38 | data.replace('\\', '\\\\')
39 | data.replace('=', '\\=')
40 | if data == '{}':
41 | data = ''
42 | return data
43 |
44 | #Make sure correct date format is provided
45 | def validateDate(date):
46 | try:
47 | datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%SZ')
48 | except ValueError:
49 | raise ValueError("Incorrect date format, should be '%Y-%m-%dT%H:%M:%SZ'")
50 |
51 | #Built CEF formatted event from audit log event dictionary
52 | def buildCEFEvent(eventDict):
53 | eventName = eventDict['event_type']['description']
54 | eventClassId = eventDict['event_type']['.tag']
55 | category = eventDict['event_category']['.tag']
56 | eventTS = datetime.datetime.strptime(eventDict['timestamp'],'%Y-%m-%dT%H:%M:%SZ')
57 | eventDetails = eventDict['details']
58 | eventActor = eventDict['actor']
59 | eventAssets = eventDict.get('assets',{})
60 | eventOrigin = eventDict.get('origin', {})
61 | ipAddress = eventOrigin.get('geo_location',{}).get('ip_address','')
62 |
63 | if eventDict['actor.tag'] in ['admin','user']:
64 | duser = eventDict['actor_info'].get('email','')
65 | duid =eventDict['actor_info'].get('team_member_id','')
66 | else:
67 | duser = ''
68 | duid = ''
69 |
70 | extensionTemplate = "duser={duser} duid={duid} cat={cat} rt={receiptTime} end={end} cs1={cs1}, cs1Label=Details of event cs2={cs2} cs2Label=Details of event actor cs3={cs3} cs3Label=Details of the event origin src={ipAddress} cs4={cs4} cs4Label=Details of event assets"
71 | extensions = extensionTemplate.format(duser=duser, duid=duid, cat=category, receiptTime=eventTS.strftime('%b %d %Y %H:%M:%S'), end=eventTS.strftime('%b %d %Y %H:%M:%S'),
72 | cs1=cleanData(eventDetails), cs2=cleanData(eventActor), cs3=cleanData(eventOrigin), ipAddress=ipAddress, cs4=cleanData(eventAssets))
73 | cef_event = cefTemplate.format(cefVersion=cefVersion, deviceVendor=deviceVendor, deviceProduct=deviceProduct, deviceVersion=deviceVersion,
74 | eventClassID=eventClassId, name=eventName, severity=severity[category], extensions=extensions)
75 | return cef_event
76 |
77 | #Write formatted cef events out to a csv
78 | def writeEvents(events):
79 | with open('dropbox_cefevents.csv', mode='w') as cefEvents:
80 | print('Writing events to dropbox_cefevents.csv in current directory')
81 | csvwriter = csv.writer(cefEvents)
82 | csvwriter.writerows(events)
83 |
84 | #Send syslog message via UDP
85 | def sendEvents(events, host, port):
86 | level = 6 #Information
87 | facility = 3 #Daemon
88 | syslog_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
89 | for event in events:
90 | data = "<%d>%s" % (level + facility*8, event[0])
91 | syslog_socket.sendto(data.encode('utf-8'), (host, port))
92 |
93 | #Pull out the relevant headers and content from raw events
94 | def formatEvents(events):
95 | cef_events = []
96 | for event in events:
97 | formatEvent = {}
98 | eventHeaders = event.keys()
99 | for header in eventHeaders:
100 | formatEvent[header] = event[header]
101 |
102 | if header == 'actor':
103 | formatEvent[header+'.tag'] = event[header]['.tag']
104 | formatEvent[header+"_info"] = event[header].get('user', event[header].get('admin'))
105 |
106 | cef = buildCEFEvent(formatEvent)
107 | cef_events.append([cef])
108 | return cef_events
109 |
110 | def main():
111 | if args.end_time:
112 | validateDate(args.end_time)
113 | if args.start_time:
114 | validateDate(args.start_time)
115 | result = getEvents(args.category, args.cursor)
116 | events = formatEvents(result[0])
117 | if args.output == True:
118 | writeEvents(events)
119 | if (args.host and args.port) is not None:
120 | print("Sending " +str(len(events))+ " activity log events")
121 | sendEvents(events, args.host, args.port)
122 | print("Event cursor: "+ result[1])
123 |
124 | if __name__ == '__main__':
125 | #Setting up argument parser for command-line options
126 | parser = argparse.ArgumentParser(description='Provide your Dropbox Business API App token and pull events from the Dropbox audit log to send to syslog server.')
127 | parser.add_argument('-c','--category', help='The category of events you want to pull from audit log.',
128 | choices=['apps', 'comments', 'devices', 'domains', 'file_operations', 'file_requests', 'groups', 'logins', 'members', 'paper',
129 | 'passwords', 'reports', 'sharing', 'showcase', 'sso', 'team_folders', 'team_policies', 'team_profile', 'tfa', 'trusted_teams'], default='')
130 | parser.add_argument('-l','--limit', help='The max amount of events to pull from the audit log at one time, default is 1000.', default=1000, type=int)
131 | parser.add_argument('--output', help='Passing this will print a csv file of your query to your current directory named dropbox_cefevents.csv', dest='output', action='store_true')
132 | parser.add_argument('--host', help='The host address for the designated syslog destination to send events.',type=str)
133 | parser.add_argument('--port', help='The port address for the designated syslog destination to send events.',type=int)
134 | parser.add_argument('-st','--start_time', help='The starting time from when to include events (inclusive), format="%%Y-%%m-%%dT%%H:%%M:%%SZ"')
135 | parser.add_argument('-et','--end_time', help='The end time from when to include events (exclusive), format="%%Y-%%m-%%dT%%H:%%M:%%SZ"')
136 | parser.add_argument('-csr', '--cursor', help='Pass event cursor from activity log, to pull new events from last query.', default=None)
137 | parser.set_defaults(output=False)
138 | args = parser.parse_args()
139 |
140 | #Reading from settings
141 | config = configparser.ConfigParser()
142 | config.read("parserSettings.ini")
143 | defaultConfig= config['DEFAULT']
144 |
145 |
146 | #Severity Mapping
147 | severity = {"comments":0, "paper":1, "showcase":1, "file_requests":2, "file_operations":3, "devices":4, "sharing":4, "team_profile":5, "apps":5,
148 | "groups":5, "domains":6, "team_folders":6, "logins": 6, "members": 6, "passwords":7, "reports":7, "sso":7, "trusted_teams":7, "team_policies":9, "tfa":9}
149 |
150 | #Static CEF Info
151 | cefTemplate = 'CEF:{cefVersion}|{deviceVendor}|{deviceProduct}|{deviceVersion}|{eventClassID}|{name}|{severity}|{extensions}'
152 | deviceVendor = 'Dropbox'
153 | cefVersion = '0'
154 | deviceProduct = 'Dropbox Activity Log'
155 | deviceVersion = '1'
156 | main()
157 |
--------------------------------------------------------------------------------
/Blog/performant_upload/performant_upload.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """Example code for uploading a local folder of files to Dropbox using the Dropbox API in a
4 | performant manner."""
5 |
6 | from concurrent.futures import ThreadPoolExecutor
7 |
8 | import configparser
9 | import logging
10 | import os
11 | import time
12 |
13 | import dropbox
14 |
15 | logging.basicConfig()
16 | logging.getLogger().setLevel(logging.DEBUG)
17 |
18 | MB = 1024 * 1024
19 |
20 |
21 | def get_client(config):
22 | """Returns the Dropbox client to use to upload files."""
23 |
24 | if not (
25 | config.get('AUTHORIZATION', 'access_token', fallback=None) or
26 | config.get('AUTHORIZATION', 'refresh_token', fallback=None)
27 | ):
28 | raise Exception("Either an access token or refresh token/app key is required.")
29 |
30 | if config.get('AUTHORIZATION', 'refresh_token', fallback=None):
31 | if not config.get('AUTHORIZATION', 'app_key', fallback=None):
32 | raise Exception("App key is required when using a refresh token.")
33 | return dropbox.Dropbox(
34 | oauth2_refresh_token=config.get('AUTHORIZATION', 'refresh_token', fallback=None),
35 | app_key=config.get('AUTHORIZATION', 'app_key', fallback=None),
36 | # the app secret is required for refresh tokens not acquired using PKCE
37 | app_secret=config.get('AUTHORIZATION', 'app_secret', fallback=None)
38 | )
39 |
40 | return dropbox.Dropbox(
41 | oauth2_access_token=config.get('AUTHORIZATION', 'access_token', fallback=None)
42 | )
43 |
44 |
45 | def collect_files(folder_path):
46 | """Returns the list of files to upload."""
47 |
48 | folder_path = os.path.expanduser(folder_path)
49 |
50 | # List all of the files inside the specified folder.
51 | files = sorted(
52 | [os.path.join(folder_path, f)
53 | for f in os.listdir(folder_path)
54 | if os.path.isfile(os.path.join(folder_path, f)) # ignores folders
55 | and f not in [".DS_Store", ".localized", ".gitignore"] # ignores system files, etc.
56 | ]
57 | )
58 |
59 | logging.info(f"Collected {str(len(files))} files for upload: {files}")
60 |
61 | return files
62 |
63 |
64 | def upload_session_appends(client, session_id, source_file_path, config):
65 | """Performs parallelized upload session appends for one file."""
66 |
67 | futures = []
68 |
69 | dest_file_name = os.path.basename(source_file_path)
70 | dest_folder = config.get('PATHS', 'remote_path').lstrip("/")
71 |
72 | logging.info(f"Using upload session with ID '{session_id}' for file '{dest_file_name}'.")
73 |
74 | with open(source_file_path, "rb") as local_file:
75 |
76 | file_size = os.path.getsize(source_file_path)
77 |
78 | def append(dest_file_name, data, cursor, close):
79 | logging.debug(f"Appending to upload session with ID '{cursor.session_id}'"
80 | f" for file '{dest_file_name}'"
81 | f" at offset: {str(cursor.offset)}")
82 | client.files_upload_session_append_v2(f=data,
83 | cursor=cursor,
84 | close=close)
85 | logging.debug(f"Done appending to upload session with ID '{cursor.session_id}'"
86 | f" for file '{dest_file_name}'"
87 | f" at offset: {str(cursor.offset)}")
88 |
89 | if file_size > 0: # For non-empty files, start a number of concurrent append calls.
90 | with ThreadPoolExecutor(
91 | max_workers=config.getint('LIMITS', 'concurrent_thread_count')
92 | ) as session_executor:
93 | while local_file.tell() < file_size:
94 | cursor = dropbox.files.UploadSessionCursor(
95 | session_id=session_id, offset=local_file.tell())
96 | data = local_file.read(config.getint('LIMITS', 'chunk_size'))
97 | close = local_file.tell() == file_size
98 | futures.append(
99 | session_executor.submit(append, dest_file_name, data, cursor, close))
100 | else: # For empty files, just call append once to close the upload session.
101 | cursor = dropbox.files.UploadSessionCursor(session_id=session_id, offset=0)
102 | append(dest_file_name=dest_file_name, data=None, cursor=cursor, close=True)
103 |
104 | for future in futures:
105 | try:
106 | future.result()
107 | except Exception as append_exception:
108 | logging.error(f"Upload session with ID '{cursor.session_id}' failed.")
109 | raise append_exception
110 |
111 | return dropbox.files.UploadSessionFinishArg(
112 | cursor=dropbox.files.UploadSessionCursor(
113 | session_id=session_id, offset=local_file.tell()),
114 | commit=dropbox.files.CommitInfo(path=f"/{dest_folder}/{dest_file_name}"))
115 |
116 |
117 | def upload_files(client, files, config):
118 | """Performs upload sessions for a batch of files in parallel."""
119 |
120 | futures = []
121 | entries = []
122 | uploaded_size = 0
123 |
124 | assert len(entries) <= 1000, "Max batch size is 1000."
125 | assert config.getint('LIMITS', 'chunk_size') % (4 * MB) == 0, \
126 | "Chunk size must be a multiple of 4 MB to use concurrent upload sessions"
127 |
128 | logging.info(f"Starting batch of {str(len(files))} upload sessions.")
129 | start_batch_result = client.files_upload_session_start_batch(
130 | num_sessions=len(files),
131 | session_type=dropbox.files.UploadSessionType.concurrent)
132 |
133 | with ThreadPoolExecutor(
134 | max_workers=config.getint('LIMITS', 'batch_thread_count')
135 | ) as batch_executor:
136 | for index, file in enumerate(files):
137 | futures.append(
138 | batch_executor.submit(
139 | upload_session_appends,
140 | client, start_batch_result.session_ids[index], file, config
141 | )
142 | )
143 |
144 | for future in futures:
145 | entries.append(future.result())
146 | uploaded_size += future.result().cursor.offset
147 |
148 | logging.info(f"Finishing batch of {str(len(entries))} entries.")
149 | finish_launch = client.files_upload_session_finish_batch(entries=entries)
150 |
151 | if finish_launch.is_async_job_id():
152 | logging.info(f"Polling for status of batch of {str(len(entries))} entries...")
153 | while True:
154 | finish_job = client.files_upload_session_finish_batch_check(
155 | async_job_id=finish_launch.get_async_job_id())
156 | if finish_job.is_in_progress():
157 | time.sleep(.5)
158 | else:
159 | complete = finish_job.get_complete()
160 | break
161 | if finish_launch.is_complete():
162 | complete = finish_launch.get_complete()
163 | elif finish_launch.is_other():
164 | raise Exception("Unknown finish result type!")
165 |
166 | logging.info(f"Finished batch of {str(len(entries))} entries.")
167 |
168 | for index, entry in enumerate(complete.entries):
169 | if entry.is_success():
170 | logging.info(f"File successfully uploaded to '{entry.get_success().path_lower}'.")
171 | elif entry.is_failure():
172 | logging.error(f"Commit for path '{entries[index].commit.path}'"
173 | f" failed due to: {entry.get_failure()}")
174 |
175 | return uploaded_size
176 |
177 |
178 | def run_and_time_uploads():
179 | """Performs and times the uploads for the folder of files."""
180 |
181 | config = configparser.ConfigParser()
182 | config.read('config.ini')
183 |
184 | client = get_client(config=config)
185 | files = collect_files(folder_path=config.get('PATHS', 'local_path'))
186 |
187 | start_time = time.time()
188 | uploaded_size = upload_files(client=client, files=files, config=config)
189 | end_time = time.time()
190 |
191 | time_elapsed = end_time - start_time
192 | logging.info(f"Uploaded {uploaded_size} bytes in {time_elapsed:.2f} seconds.")
193 |
194 | megabytes_uploaded = uploaded_size / MB
195 | logging.info(f"Approximate overall speed: {megabytes_uploaded / time_elapsed:.2f} MB/s.")
196 |
197 |
198 | if __name__ == '__main__':
199 | run_and_time_uploads()
200 |
--------------------------------------------------------------------------------
/Blog/Image-flipping-extension/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Dropbox, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 |
16 | Apache License
17 |
18 | Version 2.0, January 2004
19 |
20 | http://www.apache.org/licenses/
21 |
22 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
23 |
24 | 1. Definitions.
25 |
26 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
27 |
28 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
29 |
30 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
31 |
32 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
33 |
34 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
35 |
36 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
37 |
38 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
41 |
42 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
43 |
44 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
45 |
46 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
47 |
48 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
49 |
50 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
51 |
52 | You must give any other recipients of the Work or Derivative Works a copy of this License; and
53 | You must cause any modified files to carry prominent notices stating that You changed the files; and
54 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
55 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
56 |
57 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
58 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
59 |
60 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
61 |
62 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides 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.
63 |
64 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
65 |
66 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
67 |
68 | END OF TERMS AND CONDITIONS
69 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/components/FileBrowser.css:
--------------------------------------------------------------------------------
1 | .file-browser {
2 | max-width: 1200px;
3 | margin: 20px auto;
4 | padding: 2rem;
5 | background: rgba(42, 42, 42, 0.7);
6 | backdrop-filter: blur(10px);
7 | -webkit-backdrop-filter: blur(10px);
8 | min-height: calc(100vh - 40px);
9 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
10 | border: 1px solid rgba(255, 255, 255, 0.1);
11 | border-radius: 16px;
12 | }
13 |
14 | .top-bar {
15 | display: flex;
16 | justify-content: space-between;
17 | align-items: center;
18 | margin-bottom: 2rem;
19 | padding-bottom: 1rem;
20 | border-bottom: 1px solid rgba(255, 255, 255, 0.1);
21 | }
22 |
23 | .top-bar h2 {
24 | font-size: 1.8rem;
25 | color: rgba(255, 255, 255, 0.95);
26 | margin: 0;
27 | font-weight: 600;
28 | text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
29 | }
30 |
31 | .top-bar-actions {
32 | display: flex;
33 | align-items: center;
34 | gap: 1rem;
35 | }
36 |
37 | .namespace-toggle {
38 | display: flex;
39 | align-items: center;
40 | gap: 0.5rem;
41 | color: rgba(255, 255, 255, 0.9);
42 | }
43 |
44 | .switch {
45 | position: relative;
46 | display: inline-block;
47 | width: 50px;
48 | height: 28px;
49 | }
50 |
51 | .switch input {
52 | opacity: 0;
53 | width: 0;
54 | height: 0;
55 | }
56 |
57 | .slider {
58 | position: absolute;
59 | cursor: pointer;
60 | top: 0;
61 | left: 0;
62 | right: 0;
63 | bottom: 0;
64 | background-color: #ccc;
65 | transition: .4s;
66 | border-radius: 28px;
67 | }
68 |
69 | .slider:before {
70 | position: absolute;
71 | content: "";
72 | height: 20px;
73 | width: 20px;
74 | left: 4px;
75 | bottom: 4px;
76 | background-color: white;
77 | transition: .4s;
78 | border-radius: 50%;
79 | }
80 |
81 | input:checked + .slider {
82 | background-color: #0061fe;
83 | }
84 |
85 | input:focus + .slider {
86 | box-shadow: 0 0 1px #0061fe;
87 | }
88 |
89 | input:checked + .slider:before {
90 | transform: translateX(22px);
91 | }
92 |
93 | .switch input:disabled + .slider {
94 | background-color: #ccc;
95 | cursor: not-allowed;
96 | opacity: 0.6;
97 | }
98 |
99 | .switch input:disabled + .slider:before {
100 | background-color: #f5f5f5;
101 | }
102 |
103 | .namespace-toggle.disabled {
104 | opacity: 0.6;
105 | cursor: not-allowed;
106 | }
107 |
108 | .team-selector {
109 | display: flex;
110 | align-items: center;
111 | gap: 0.5rem;
112 | background: rgba(51, 51, 51, 0.5);
113 | padding: 0.75rem;
114 | border-radius: 12px;
115 | border: 1px solid rgba(255, 255, 255, 0.1);
116 | backdrop-filter: blur(4px);
117 | -webkit-backdrop-filter: blur(4px);
118 | min-width: 280px;
119 | }
120 |
121 | .team-loading,
122 | .team-error {
123 | color: rgba(255, 255, 255, 0.9);
124 | font-size: 0.9rem;
125 | padding: 8px 12px;
126 | background: rgba(64, 64, 64, 0.5);
127 | border-radius: 8px;
128 | flex-grow: 1;
129 | }
130 |
131 | .team-loading {
132 | display: flex;
133 | align-items: center;
134 | gap: 0.5rem;
135 | }
136 |
137 | .team-error {
138 | color: #ff6b6b;
139 | }
140 |
141 | .team-icon {
142 | color: rgba(0, 97, 254, 0.9);
143 | }
144 |
145 | .team-selector select {
146 | padding: 8px 12px;
147 | font-size: 0.9rem;
148 | background: rgba(64, 64, 64, 0.5);
149 | color: rgba(255, 255, 255, 0.9);
150 | border: 1px solid rgba(255, 255, 255, 0.1);
151 | border-radius: 8px;
152 | cursor: pointer;
153 | min-width: 220px;
154 | backdrop-filter: blur(4px);
155 | -webkit-backdrop-filter: blur(4px);
156 | transition: all 0.3s ease;
157 | }
158 |
159 | .team-selector select:hover {
160 | background: rgba(80, 80, 80, 0.7);
161 | border-color: rgba(0, 97, 254, 0.5);
162 | }
163 |
164 | .team-selector select:focus {
165 | outline: none;
166 | border-color: rgba(0, 97, 254, 0.7);
167 | box-shadow: 0 0 0 2px rgba(0, 97, 254, 0.2);
168 | }
169 |
170 | .navigation-bar {
171 | display: flex;
172 | align-items: center;
173 | gap: 0.75rem;
174 | background: rgba(51, 51, 51, 0.5);
175 | backdrop-filter: blur(4px);
176 | -webkit-backdrop-filter: blur(4px);
177 | padding: 0.75rem 1rem;
178 | border-radius: 12px;
179 | margin-bottom: 1.5rem;
180 | border: 1px solid rgba(255, 255, 255, 0.1);
181 | }
182 |
183 | .breadcrumbs {
184 | flex-grow: 1;
185 | font-size: 0.95rem;
186 | color: rgba(255, 255, 255, 0.7);
187 | }
188 |
189 | .breadcrumb-part {
190 | color: #0061fe;
191 | cursor: pointer;
192 | padding: 0.25rem 0.5rem;
193 | border-radius: 6px;
194 | transition: all 0.3s ease;
195 | }
196 |
197 | .breadcrumb-part:hover {
198 | background: rgba(64, 64, 64, 0.5);
199 | text-decoration: none;
200 | }
201 |
202 | .upload-section {
203 | margin-bottom: 1.5rem;
204 | padding: 1.5rem;
205 | background: rgba(51, 51, 51, 0.5);
206 | backdrop-filter: blur(4px);
207 | -webkit-backdrop-filter: blur(4px);
208 | border-radius: 12px;
209 | border: 2px dashed rgba(255, 255, 255, 0.1);
210 | transition: all 0.3s ease;
211 | }
212 |
213 | .upload-section:hover {
214 | border-color: rgba(0, 97, 254, 0.5);
215 | background: rgba(56, 56, 56, 0.6);
216 | }
217 |
218 | .file-input {
219 | display: none;
220 | }
221 |
222 | .file-list {
223 | border: 1px solid rgba(255, 255, 255, 0.1);
224 | border-radius: 12px;
225 | overflow: hidden;
226 | background: rgba(51, 51, 51, 0.5);
227 | backdrop-filter: blur(4px);
228 | -webkit-backdrop-filter: blur(4px);
229 | }
230 |
231 | .file-item,
232 | .file-list-header {
233 | display: grid;
234 | grid-template-columns: minmax(0, 1fr) 120px 200px 120px;
235 | align-items: center;
236 | padding: 1rem;
237 | border-bottom: 1px solid rgba(255, 255, 255, 0.1);
238 | transition: all 0.3s ease;
239 | gap: 1rem;
240 | }
241 |
242 | .file-list-header {
243 | color: rgba(255, 255, 255, 0.7);
244 | font-size: 0.85rem;
245 | font-weight: 600;
246 | text-transform: uppercase;
247 | letter-spacing: 0.5px;
248 | border-bottom: 1px solid rgba(255, 255, 255, 0.2);
249 | }
250 |
251 | .file-item:hover {
252 | background: rgba(56, 56, 56, 0.6);
253 | }
254 |
255 | .file-item:last-child {
256 | border-bottom: none;
257 | }
258 |
259 | .file-info {
260 | display: flex;
261 | align-items: center;
262 | gap: 0.75rem;
263 | min-width: 0;
264 | }
265 |
266 | .folder-icon {
267 | color: rgba(0, 97, 254, 0.9);
268 | font-size: 1.1rem;
269 | flex-shrink: 0;
270 | }
271 |
272 | .file-icon {
273 | color: rgba(255, 255, 255, 0.6);
274 | font-size: 1.1rem;
275 | flex-shrink: 0;
276 | }
277 |
278 | .file-name {
279 | overflow: hidden;
280 | text-overflow: ellipsis;
281 | white-space: nowrap;
282 | font-size: 0.95rem;
283 | color: rgba(255, 255, 255, 0.9);
284 | }
285 |
286 | .folder-name {
287 | cursor: pointer;
288 | color: rgba(0, 97, 254, 0.9);
289 | font-weight: 500;
290 | }
291 |
292 | .folder-name:hover {
293 | text-decoration: underline;
294 | }
295 |
296 | .file-size,
297 | .header-size,
298 | .file-modified,
299 | .header-modified {
300 | text-align: center;
301 | font-size: 0.9rem;
302 | color: rgba(255, 255, 255, 0.6);
303 | white-space: nowrap;
304 | }
305 |
306 | .header-size,
307 | .header-modified {
308 | color: rgba(255, 255, 255, 0.7);
309 | font-weight: 600;
310 | }
311 |
312 | .file-actions,
313 | .header-actions {
314 | display: flex;
315 | justify-content: flex-end;
316 | gap: 0.5rem;
317 | }
318 |
319 | .loading,
320 | .empty-folder {
321 | text-align: center;
322 | padding: 3rem;
323 | color: rgba(255, 255, 255, 0.6);
324 | font-size: 0.95rem;
325 | background: rgba(51, 51, 51, 0.5);
326 | backdrop-filter: blur(4px);
327 | -webkit-backdrop-filter: blur(4px);
328 | border-radius: 12px;
329 | margin: 1rem 0;
330 | border: 1px solid rgba(255, 255, 255, 0.1);
331 | }
332 |
333 | .error-container {
334 | text-align: center;
335 | padding: 3rem;
336 | background: rgba(58, 42, 42, 0.5);
337 | backdrop-filter: blur(4px);
338 | -webkit-backdrop-filter: blur(4px);
339 | border-radius: 12px;
340 | margin: 1rem 0;
341 | border: 1px solid rgba(255, 77, 77, 0.2);
342 | }
343 |
344 | .error-message {
345 | color: rgba(255, 107, 107, 0.9);
346 | margin-bottom: 1.5rem;
347 | font-size: 0.95rem;
348 | }
349 |
350 | /* Toast Styles */
351 | .toast {
352 | position: fixed;
353 | top: 20px;
354 | right: 20px;
355 | padding: 12px 24px;
356 | border-radius: 8px;
357 | color: white;
358 | font-size: 0.95rem;
359 | z-index: 1000;
360 | animation: slideIn 0.3s ease-out forwards;
361 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
362 | max-width: 400px;
363 | word-break: break-word;
364 | }
365 |
366 | .toast.success {
367 | background-color: #10B981;
368 | border: 1px solid rgba(16, 185, 129, 0.2);
369 | }
370 |
371 | .toast.error {
372 | background-color: #EF4444;
373 | border: 1px solid rgba(239, 68, 68, 0.2);
374 | }
375 |
376 | @keyframes slideIn {
377 | from {
378 | transform: translateX(100%);
379 | opacity: 0;
380 | }
381 | to {
382 | transform: translateX(0);
383 | opacity: 1;
384 | }
385 | }
386 |
387 | /* Responsive Design */
388 | @media (max-width: 768px) {
389 | .file-browser {
390 | margin: 0;
391 | padding: 1rem;
392 | border-radius: 0;
393 | }
394 |
395 | .file-item {
396 | grid-template-columns: 1fr;
397 | gap: 0.75rem;
398 | }
399 |
400 | .file-details {
401 | justify-content: flex-start;
402 | }
403 |
404 | .file-actions {
405 | justify-content: flex-start;
406 | }
407 |
408 | .navigation-bar {
409 | flex-wrap: wrap;
410 | }
411 |
412 | .breadcrumbs {
413 | width: 100%;
414 | margin-top: 0.5rem;
415 | }
416 |
417 | .team-selector {
418 | width: 100%;
419 | }
420 |
421 | .team-selector select {
422 | width: 100%;
423 | }
424 | }
--------------------------------------------------------------------------------
/Blog/Image-flipping-extension/controller.js:
--------------------------------------------------------------------------------
1 | // Redirect URL to pass to Dropbox. Has to be whitelisted in Dropbox settings
2 | OAUTH_REDIRECT_URL='http://localhost:3000/auth';
3 |
4 | // Libraries used during authentication
5 | crypto = require('crypto'), // To create random state values for OAuth
6 | NodeCache = require( "node-cache" ), // To cache OAuth state parameter
7 |
8 | // Libraries to manipulate images and the File System
9 | Jimp = require('jimp'), // Library to manipulate images
10 | fs = require('fs'), // To read the file system
11 |
12 | // Dropbox libraries
13 | Dropbox = require('dropbox').Dropbox,
14 | fetch = require('isomorphic-fetch');
15 |
16 | // Constants required to recreate image paths
17 | const
18 | backend_path = "public/images/",
19 | client_path = "images/";
20 |
21 | const mycache = new NodeCache();
22 |
23 | //-- ROUTED FUNCTIONS --
24 |
25 | module.exports.home = async (req, res)=>{
26 |
27 | let dbx = getDropboxInstance(req);
28 |
29 | if(!req.session.token){
30 | authorize(dbx,req,res);
31 | } else {
32 | // If no file selected instruct user to pick one
33 | if(!req.session.dbx_file){
34 | res.send("You need to pick a file from Dropbox");
35 | }
36 | // If user was already editing an image, present it
37 | else if(req.session.dbx_file.cached){
38 | presentImages(req,res);
39 | }
40 | //If user started the edit flow, but was redirected to OAuth, download images
41 | else if (req.session.dbx_file.id){
42 | prepareImages(dbx,req,res);
43 | }
44 | }
45 | }
46 |
47 | // Redirect from Dropbox after OAuth
48 | module.exports.auth = async (req, res)=>{
49 | if(req.query.error_description){
50 | console.log(req.query.error_description);
51 | res.status(500);
52 | return res.send("
Error... yikes!
Check your console!
");
53 | }
54 |
55 | // OAuth state value is only valid for 10 minutes
56 | // Session that created the state should be the same as the current session
57 | let state = req.query.state;
58 | let session_id = mycache.get(state);
59 | if(!session_id){
60 | res.status(440);
61 | return res.send("
Authentication timeout, please try again
");
62 | }else if (session_id != req.session.id){
63 | res.status(500);
64 | console.log("Authorization flow was started under a different session");
65 | return res.send("
Error... yikes!
Check your console!
");
66 | }
67 |
68 | if(req.query.code){
69 |
70 | let dbx = getDropboxInstance(req);
71 |
72 | try{
73 |
74 | let token = await dbx.getAccessTokenFromCode(OAUTH_REDIRECT_URL, req.query.code);
75 |
76 | // Store token and invalidate the state
77 | req.session.token = token;
78 | mycache.del(state);
79 |
80 | // Get the root_namespace for the user
81 | // Ensures that this flow works for Dropbox Business users team spaces
82 | // More info https://www.dropbox.com/developers/reference/namespace-guide
83 | dbx.setAccessToken(token);
84 | let account = await dbx.usersGetCurrentAccount();
85 | req.session.root_namespace_id = account.root_info.root_namespace_id;
86 |
87 | // Additionally save the user name to display it later
88 | req.session.name = account.name.given_name;
89 |
90 | res.redirect('/');
91 |
92 | }catch(error){
93 | console.log(error);
94 | res.status(500);
95 | res.send("
Error... yikes!
Check your console!
");
96 | }
97 | }
98 | }
99 |
100 | // Called when a file action is triggered by Dropbox
101 | module.exports.fileAction = (req,res)=>{
102 |
103 | // A file_id is required
104 | if(!req.query.file_id){
105 | res.status(400);
106 | return res.send("
This action requires a file_id");
107 | }
108 |
109 | let dbx = getDropboxInstance(req);
110 |
111 | // Store the file_id in the current session
112 | req.session.dbx_file = {
113 | id : req.query.file_id
114 | }
115 |
116 | // If cookies are cleared or session expires we need to authenticate again
117 | if(!req.session.token){
118 | authorize(dbx,req,res);
119 | }else{
120 | prepareImages(dbx,req,res);
121 | }
122 | }
123 |
124 | // Saves the edited file in the current session to Dropbox in the same folder
125 | module.exports.saveToDropbox = async (req,res)=>{
126 |
127 | let dbx = getDropboxInstance(req);
128 |
129 | let file_id = req.session.dbx_file.id;
130 | let file_name = req.session.dbx_file.name;
131 | let path_lower = req.session.dbx_file.path_lower;
132 |
133 | // Append an edited note to the name of the file before the file extension
134 | let dbx_save_path = path_lower.replace(/\./g,"(edited - FlipImage).");
135 |
136 | // Server location of the flipped image
137 | let flipped_img_path = backend_path + file_id + "_flipped_" + file_name;
138 |
139 | try{
140 |
141 | let content = fs.readFileSync(flipped_img_path);
142 |
143 | let upload_params = {
144 | 'contents': content,
145 | 'path' : dbx_save_path,
146 | 'strict_conflict': true, // Force to create a copy
147 | 'autorename': true // Autorename if a copy is created
148 | }
149 |
150 | // UPload file and wait for it to be finished
151 | let upload_response = await dbx.filesUpload(upload_params);
152 |
153 | // Grab a link so we can send the user back to Dropbox
154 | let sharedlink_response = await dbx.sharingCreateSharedLinkWithSettings({'path': upload_response.id});
155 |
156 | // Cleanup the session upon success and redirect user to Dropbox
157 | req.session.dbx_file = null;
158 | res.redirect(sharedlink_response.url);
159 |
160 | }catch(error){
161 | // You should handle here possible Dropbox related errors
162 | // See Dropbox documentation for possible errors
163 | console.log(error);
164 | res.status(500);
165 | res.send("
Error... yikes!
Check your console!
");
166 | }
167 |
168 | }
169 |
170 | // -- INTERNAL FUNCTIONS --
171 |
172 | // Kick starts the OAuth code exchange
173 | function authorize(dbx,req,res,require_role){
174 | // Create a random state value
175 | let state = crypto.randomBytes(16).toString('hex');
176 | // Save state and the session id for 10 mins
177 | mycache.set(state, req.session.id, 6000);
178 | // Get authentication URL and redirect
179 | authUrl = dbx.getAuthenticationUrl(OAUTH_REDIRECT_URL, state, 'code');
180 | // Attach a require_role parameter if present
181 | if(req.query.require_role){
182 | authUrl = authUrl + "&require_role=" + req.query.require_role;
183 | }
184 |
185 | res.redirect(authUrl);
186 | }
187 |
188 | // Gets a new instance of Dropbox for this user session
189 | function getDropboxInstance(req){
190 |
191 | let dbx_config = {
192 | fetch: fetch,
193 | clientId: process.env.DBX_APP_KEY,
194 | clientSecret: process.env.DBX_APP_SECRET
195 | };
196 |
197 | let dbx = new Dropbox(dbx_config);
198 |
199 | // Set the root namespace for this user
200 | // Ensures that this flow works for Dropbox Business users team folders
201 | // More info https://www.dropbox.com/developers/reference/namespace-guide
202 | if(req.session.root_namespace_id){
203 | dbx.pathRoot = JSON.stringify({".tag": "root", "root": req.session.root_namespace_id});
204 | }
205 |
206 | if(req.session.token){
207 | dbx.setAccessToken(req.session.token);
208 | }
209 |
210 | return dbx;
211 | }
212 |
213 | // Downloads the original image from Dropbox and creates a flipped one
214 | // Both will be placed in a public folder that can be reached by client
215 | async function prepareImages(dbx,req,res){
216 |
217 | try{
218 |
219 | // Download file using the file_id as path
220 | let response = await dbx.filesDownload({'path':req.session.dbx_file.id});
221 |
222 | // Additional information needed when file is saved back
223 | let file_name = response.name;
224 | req.session.dbx_file.name = file_name;
225 |
226 | let path_lower = response.path_lower;
227 | req.session.dbx_file.path_lower = path_lower;
228 |
229 | // First download the file and put it in the local public folder
230 | let original_temp_name = req.session.dbx_file.id + "_" + file_name;
231 | fs.writeFileSync(backend_path + original_temp_name, response.fileBinary);
232 |
233 | // Then create a flipped copy using JIMP
234 | let img_copy = await Jimp.read(backend_path + original_temp_name);
235 | await img_copy.flip(true,true);
236 | let flipped_temp_name = req.session.dbx_file.id + "_flipped_" + file_name;
237 | await(img_copy.writeAsync(backend_path + flipped_temp_name));
238 |
239 | // Indicate files have been downloaded
240 | req.session.dbx_file.cached = true;
241 |
242 | presentImages(req,res);
243 |
244 | }catch(error){
245 |
246 | // Dropbox tokens are short lived, so if expired grab a new one
247 | if(error.response && error.response.status == 401){
248 | authorize(req,res);
249 | }else{
250 | // You should handle here possible Dropbox download related errors
251 | // See Dropbox documentation for possible errors
252 | // https://dropbox.github.io/dropbox-sdk-js/global.html#FilesLookupError
253 | console.log(error);
254 | res.status(500);
255 | res.send("
Error... yikes!
Check your console!
");
256 | }
257 | }
258 | }
259 |
260 | // Presents the images to the user
261 | function presentImages(req,res){
262 |
263 | let original_temp_name = req.session.dbx_file.id + "_" + req.session.dbx_file.name;
264 | let flipped_temp_name = req.session.dbx_file.id + "_flipped_" + req.session.dbx_file.name;
265 |
266 | let args = {
267 | dropbox_name: req.session.name,
268 | image_original_path: client_path + original_temp_name,
269 | image_transformed_path: client_path + flipped_temp_name
270 | }
271 |
272 | res.render('index', args);
273 | }
--------------------------------------------------------------------------------
/Blog/performant_upload/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Blog/list_all_team_contents/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Blog/Convert-activity-log-to-CEF-events/license.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Sample Apps/JavaScript/file_browser/src/contexts/AuthContext.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * AuthContext.jsx - Authentication Context Provider
3 | * This file implements the authentication context and provider for the Dropbox integration.
4 | * It handles user authentication, token management, and team-based authentication features.
5 | */
6 |
7 | import React, { createContext, useContext, useState, useEffect } from 'react';
8 | import {
9 | createDropboxClient,
10 | getTeamMembers,
11 | getCurrentAccount,
12 | getAdminMemberId
13 | } from '../utils/dropboxClient';
14 |
15 | /**
16 | * Dropbox configuration object containing client ID and redirect URI
17 | * Values are loaded from environment variables
18 | */
19 | export const DROPBOX_CONFIG = {
20 | clientId: import.meta.env.VITE_DROPBOX_APP_KEY,
21 | redirectUri: import.meta.env.VITE_DROPBOX_REDIRECT_URI,
22 | };
23 |
24 | /**
25 | * Custom hook that syncs state with localStorage
26 | * @param {string} key - localStorage key
27 | * @param {any} initialValue - Initial value if no value exists in localStorage
28 | * @returns {[any, Function]} - State value and setter function
29 | */
30 | function useLocalStorage(key, initialValue) {
31 | // Initialize state with value from localStorage or initial value
32 | const [value, setValue] = useState(() => {
33 | try {
34 | const item = localStorage.getItem(key);
35 | return item ? JSON.parse(item) : initialValue;
36 | } catch (error) {
37 | console.error(`Error reading localStorage key "${key}":`, error);
38 | return initialValue;
39 | }
40 | });
41 |
42 | // Update localStorage when state changes
43 | useEffect(() => {
44 | try {
45 | if (value === null || value === undefined) {
46 | localStorage.removeItem(key);
47 | } else {
48 | localStorage.setItem(key, JSON.stringify(value));
49 | }
50 | } catch (error) {
51 | console.error(`Error writing to localStorage key "${key}":`, error);
52 | }
53 | }, [key, value]);
54 |
55 | return [value, setValue];
56 | }
57 |
58 | // Create the authentication context
59 | const AuthContext = createContext(null);
60 |
61 | /**
62 | * AuthProvider Component
63 | * Manages authentication state and provides authentication-related functionality
64 | * to the entire application through React Context.
65 | *
66 | * @param {Object} props - Component props
67 | * @param {React.ReactNode} props.children - Child components to be wrapped with auth context
68 | */
69 | export function AuthProvider({ children }) {
70 | // Authentication state management
71 | const [accessToken, setAccessToken] = useLocalStorage('dropboxAccessToken', null);
72 | const [refreshToken, setRefreshToken] = useLocalStorage('dropboxRefreshToken', null);
73 | const [dropboxClient, setDropboxClient] = useState(null);
74 | const [loading, setLoading] = useState(true);
75 | const [hasOfflineAccess, setHasOfflineAccess] = useLocalStorage('hasOfflineAccess', false);
76 |
77 | // Team authentication state management
78 | const [isTeamAuth, setIsTeamAuth] = useLocalStorage('isTeamAuth', false);
79 | const [teamMembers, setTeamMembers] = useLocalStorage('teamMembers', []);
80 | const [selectedMember, setSelectedMember] = useLocalStorage('selectedMember', null);
81 | const [rootNamespaceId, setRootNamespaceId] = useLocalStorage('rootNamespaceId', null);
82 | const [isViewingRoot, setIsViewingRoot] = useLocalStorage('isViewingRoot', false);
83 | const [currentPath, setCurrentPath] = useLocalStorage('currentPath', '');
84 | const [pathKey, setPathKey] = useState(0);
85 | const [isLoadingTeamMembers, setIsLoadingTeamMembers] = useState(false);
86 | // Add currentAccount to state
87 | const [currentAccount, setCurrentAccount] = useState(null);
88 |
89 | /**
90 | * Effect hook for initializing the Dropbox client
91 | * Handles both individual and team-based authentication
92 | */
93 | useEffect(() => {
94 | if (!accessToken) {
95 | setLoading(false);
96 | return;
97 | }
98 |
99 | // Create client for team member access
100 | if (isTeamAuth && selectedMember) {
101 | const memberId = selectedMember.teamMemberId;
102 |
103 | // Always get the current member's account info to get the correct root namespace ID.
104 | const tempClient = createDropboxClient(accessToken, refreshToken, true, memberId);
105 | getCurrentAccount(tempClient)
106 | .then(account => {
107 | setCurrentAccount(account);
108 | const currentMemberRootNamespaceId = account.root_info.root_namespace_id;
109 |
110 | // Calculate the pathRoot for the API call right here, with the fresh ID.
111 | const correctPathRoot = isViewingRoot && currentMemberRootNamespaceId
112 | ? JSON.stringify({ ".tag": "root", "root": currentMemberRootNamespaceId })
113 | : null;
114 |
115 | // Update the global rootNamespaceId for the UI and subsequent renders.
116 | if (rootNamespaceId !== currentMemberRootNamespaceId) {
117 | setRootNamespaceId(currentMemberRootNamespaceId);
118 | }
119 | const finalClient = createDropboxClient(accessToken, refreshToken, true, memberId, correctPathRoot);
120 | setDropboxClient(finalClient);
121 | })
122 | .catch(err => {
123 | console.error("Could not get account info for selected member. Proceeding without path root.", err);
124 |
125 | // If the error is 401, it means the token is invalid for this action.
126 | // The best course of action is to log out to force re-authentication.
127 | if (err?.status === 401) {
128 | handleLogout();
129 | return; // Stop further execution in this broken state
130 | }
131 |
132 | setRootNamespaceId('');
133 | const finalClient = createDropboxClient(accessToken, refreshToken, true, memberId, null);
134 | setDropboxClient(finalClient);
135 | })
136 | .finally(() => {
137 | setLoading(false);
138 | });
139 |
140 | return;
141 | }
142 |
143 | // Initialize team authentication and fetch members
144 | if (isTeamAuth) {
145 | const basicClient = createDropboxClient(accessToken, refreshToken);
146 | setIsLoadingTeamMembers(true);
147 |
148 | Promise.all([getTeamMembers(basicClient), getAdminMemberId(basicClient)])
149 | .then(([members, adminMemberId]) => {
150 | setTeamMembers(members);
151 |
152 | if (members.length > 0 && !selectedMember) {
153 | const adminMember = members.find(m => m.teamMemberId === adminMemberId);
154 | setSelectedMember(adminMember || members[0]);
155 | } else {
156 | setLoading(false);
157 | }
158 | })
159 | .catch(error => {
160 | console.error('Failed to fetch team members:', error);
161 | setLoading(false);
162 | })
163 | .finally(() => {
164 | setIsLoadingTeamMembers(false);
165 | });
166 | return;
167 | }
168 |
169 | // Create client for individual user access
170 | if (!isTeamAuth) {
171 | const tempClient = createDropboxClient(accessToken, refreshToken);
172 | getCurrentAccount(tempClient).then(account => {
173 | setCurrentAccount(account); // Store the account info
174 | const currentRootNamespaceId = account.root_info.root_namespace_id;
175 | const homeNamespaceId = account.root_info.home_namespace_id;
176 |
177 | // Set rootNamespaceId regardless of whether it matches home namespace
178 | setRootNamespaceId(currentRootNamespaceId);
179 |
180 | // If it's a single user account (root matches home), ensure we're not in root view
181 | if (currentRootNamespaceId === homeNamespaceId) {
182 | setIsViewingRoot(false);
183 | }
184 |
185 | const correctPathRoot = isViewingRoot && currentRootNamespaceId
186 | ? JSON.stringify({ ".tag": "root", "root": currentRootNamespaceId })
187 | : null;
188 |
189 | const finalClient = createDropboxClient(accessToken, refreshToken, false, null, correctPathRoot);
190 | setDropboxClient(finalClient);
191 | }).catch(err => {
192 | console.error("Could not get account info for individual user.", err);
193 |
194 | if (err?.status === 401) {
195 | handleLogout();
196 | return;
197 | }
198 |
199 | setRootNamespaceId('');
200 | const finalClient = createDropboxClient(accessToken, refreshToken, false, null, null);
201 | setDropboxClient(finalClient);
202 | }).finally(() => {
203 | setLoading(false);
204 | });
205 | }
206 | }, [accessToken, isTeamAuth, selectedMember, isViewingRoot]);
207 |
208 |
209 | /**
210 | * Handles user login
211 | * Stores authentication data in state and local storage
212 | *
213 | * @param {Object} authData - Authentication data from Dropbox
214 | * @param {boolean} offlineAccess - Whether offline access was requested
215 | */
216 | const handleLogin = (authData, offlineAccess = false) => {
217 | const { accessToken, refreshToken, teamId: newTeamId } = authData;
218 | const isTeam = !!newTeamId;
219 |
220 | // Clear any existing auth data from storage to prevent stale state
221 | handleLogout();
222 |
223 | setAccessToken(accessToken);
224 | setRefreshToken(refreshToken);
225 | setIsTeamAuth(isTeam);
226 | setHasOfflineAccess(offlineAccess);
227 | };
228 |
229 | /**
230 | * Handles user logout
231 | * Clears all authentication data from state and local storage
232 | */
233 | const handleLogout = () => {
234 | setAccessToken(null);
235 | setRefreshToken(null);
236 | setDropboxClient(null);
237 | setIsTeamAuth(false);
238 | setTeamMembers([]);
239 | setSelectedMember(null);
240 | setRootNamespaceId(null);
241 | setIsViewingRoot(false);
242 | setCurrentPath('');
243 | setHasOfflineAccess(false);
244 | setCurrentAccount(null);
245 | };
246 |
247 | /**
248 | * Switches the active team member
249 | * Creates a new Dropbox client for the selected team member
250 | *
251 | * @param {Object} member - Selected team member information
252 | */
253 | const selectTeamMember = (member) => {
254 | if (!member || !isTeamAuth || !accessToken) return;
255 | setSelectedMember(member);
256 | setCurrentPath(''); // Reset path when switching members
257 | setPathKey(prev => prev + 1);
258 | };
259 |
260 | /**
261 | * Toggles between the user's home view and the root namespace view.
262 | * This is only available if the user is part of a team structure.
263 | */
264 | const toggleNamespaceView = () => {
265 | setIsViewingRoot(prev => !prev);
266 | setCurrentPath(''); // Reset path when toggling view
267 | setPathKey(prev => prev + 1);
268 | };
269 |
270 | /**
271 | * Triggers a reset of the pathKey, forcing a re-render of the FileBrowser component
272 | * to reset its currentPath state.
273 | */
274 | const triggerPathReset = () => {
275 | setPathKey(prev => prev + 1);
276 | };
277 |
278 | // Context value containing all authentication-related state and functions
279 | const value = {
280 | accessToken,
281 | refreshToken,
282 | dropboxClient,
283 | loading,
284 | handleLogin,
285 | handleLogout,
286 | isAuthenticated: !!accessToken,
287 | isTeamAuth,
288 | teamMembers,
289 | selectedMember,
290 | selectTeamMember,
291 | rootNamespaceId,
292 | isViewingRoot,
293 | toggleNamespaceView,
294 | pathKey,
295 | triggerPathReset,
296 | hasOfflineAccess,
297 | isLoadingTeamMembers,
298 | currentPath,
299 | setCurrentPath,
300 | currentAccount,
301 | };
302 |
303 | return {children};
304 | }
305 |
306 | /**
307 | * Custom hook for accessing authentication context
308 | * @returns {Object} Authentication context value
309 | * @throws {Error} If used outside of AuthProvider
310 | */
311 | export function useAuth() {
312 | const context = useContext(AuthContext);
313 | if (!context) {
314 | throw new Error('useAuth must be used within an AuthProvider');
315 | }
316 | return context;
317 | }
318 |
--------------------------------------------------------------------------------
/Blog/Image-flipping-extension/README.md:
--------------------------------------------------------------------------------
1 | # Image Flipping Extension Sample
2 |
3 | By Ruben Rincon
4 | July, 2020
5 |
6 | ## Background
7 |
8 | This code sample shows how to integrate [Dropbox Extensions](https://www.dropbox.com/lp/developers/reference/extensions-guide) into an application. It implements a minimalist web server able to handle multiple Dropbox users at the same time which receives actions from images in dropbox.com. Every time an action is triggered, it presents the original image selected and an upside down (flipped) version of it, along with a save option. When the user clicks on *save*, the flipped version is saved to Dropbox in the same path as the original image. Finally, a [shared link](https://www.dropbox.com/lp/developers/reference/dbx-sharing-guide) is created for the newly uploaded image, and the user is redirected to it.
9 |
10 | This simplified example flips images upside down, but an app that has more complex file transformation or analysis would follow a similar pattern.
11 |
12 | The code sample uses the following tech stack:
13 | - The server is implemented using [Node.](https://nodejs.org/en/)[js](https://nodejs.org/en/) and [Express](https://expressjs.com/). The minimum version required for Node.JS is 8.2.1
14 | - [Handlebars](https://handlebarsjs.com/), which is a minimalist template engine that allows to load simple HTML within an .hbs file, and pass JavaScript objects to it.
15 | - The Dropbox [JavaScript SDK](https://github.com/dropbox/dropbox-sdk-js) is used to make API calls to Dropbox
16 |
17 | ## Setting up the Dropbox App
18 |
19 | Before you can successfully run the code from this repository, you first need to have a Dropbox application with a registered Extension. If you don’t have an application, create one in the Dropbox developer [App Console](https://www.dropbox.com/developers/apps). Note that only apps with *Full Dropbox* access can register an Extension.
20 |
21 | If you are using a [*Scoped access*](https://www.dropbox.com/lp/developers/reference/oauth-guide#scopes) app, then you will need to mark at least the following permissions:
22 |
23 | - files.content.read
24 | - files.content.write
25 | - sharing.write
26 |
27 | ## Setting up OAuth
28 | This code sample implements OAuth code authentication flow allowing multiple users to authorize this application. For more information about implementing OAuth please refer to our [OAuth Guide](https://www.dropbox.com/lp/developers/reference/oauth-guide). To configure OAuth in your app you need to follow these two steps in the *Settings* tab in the App Console.
29 |
30 | 1. Click on *Enable additional users* if you haven’t already done so, and your app isn’t already in [production mode](https://www.dropbox.com/developers/reference/developer-guide#production-approval)
31 |
32 | 2. Enter the redirect URI http://localhost:3000/auth as shown in the following screenshot
33 |
34 | 
35 |
36 |
37 | ## Setting up the Extension
38 |
39 | Now that the app is configured, you need to add the Extension. To do that, scroll down the *Settings* page and look for the *Extensions* section. For more information about setting up an Extension check out our [Extensions Guide](https://www.dropbox.com/lp/developers/reference/extensions-guide).
40 |
41 | Add an Extension with the following configuration:
42 | - **Extension URI -** http://localhost:3000/dropbox_file_action
43 | - **What is the main purpose of this extension? -** Opening/Editing files
44 | - **Supported File Types -** .jpg, .jpeg, .png
45 | - **Max File Size (MB) -** 10
46 |
47 | 
48 |
49 | After you save it, click on *Edit* to modify the extension and **uncheck** the *Only me* option in Visibility settings. This will allow the Extension to be presented to users who link their accounts via OAuth in the Open menu and connected apps page. The final configuration of your Extension should look just like this:
50 |
51 | 
52 |
53 | Now that the extension is configured, you should be able to see the extension **in your own Dropbox account** even without going through OAuth. To test it, in dropbox.com browse any image with one of the specified file types and then click on **Open→ Connect more apps → ""**. This will trigger the file action and a new tab will be launched with the Extension URI you registered. After you link the app through OAuth, the extension will be displayed directly in the Open Menu and will be listed in the connected apps page as explained later.
54 |
55 | ## Running the code
56 |
57 | Make sure Node.JS is installed on your machine, if that is not the case, you can go to [nodejs.org](https://nodejs.org/en/) and get the latest version. Anything above 8.2.1 would work.
58 |
59 | This code sample consists of the following files:
60 | - **package.json** tracks metadata about node modules that the project needs
61 | - **app.js** entry point and application configuration
62 | - **controller.js** implements the code sample logic
63 | - **views/index.hbs** is the html page presented to user with the two images
64 |
65 | First, you need to add a **.env** file in the root of the project (where the package.json file is). Paste the following content and replace the values with your information from the *Settings* tab of the App Console
66 |
67 | **.env**
68 | ```
69 | DBX_APP_KEY = '