mirror of
https://github.com/abraunegg/onedrive
synced 2026-03-14 14:35:46 +01:00
* Adds support for calling `acquireTokenInteractively` and `acquireTokenSilently` via D-Bus * Parses and handles the full `brokerTokenResponse` object returned by the Intune broker * Stores and reuses `account` data to enable silent token refresh without repeated interactive authentication * Ensures the access token and its expiry time are properly calculated and stored for consistent token management * Fallback to interactive authentication is triggered if silent authentication fails
This commit is contained in:
parent
b496c4e21f
commit
61e5a1edb6
18 changed files with 847 additions and 175 deletions
3
.github/actions/spelling/allow.txt
vendored
3
.github/actions/spelling/allow.txt
vendored
|
|
@ -157,6 +157,7 @@ hnsecs
|
|||
howto
|
||||
hskrieg
|
||||
htons
|
||||
ietf
|
||||
idk
|
||||
idlol
|
||||
idup
|
||||
|
|
@ -195,6 +196,7 @@ lgio
|
|||
lglib
|
||||
lgobject
|
||||
libcrypto
|
||||
libdbus
|
||||
libdir
|
||||
libexec
|
||||
libexecdir
|
||||
|
|
@ -280,6 +282,7 @@ onetoc
|
|||
onlydeps
|
||||
onmicrosoft
|
||||
ontextmessage
|
||||
oob
|
||||
opensuse
|
||||
overallocated
|
||||
pacman
|
||||
|
|
|
|||
2
.github/workflows/testbuild.yaml
vendored
2
.github/workflows/testbuild.yaml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
run: sudo apt install -y build-essential
|
||||
|
||||
- name: Install build-dependencies
|
||||
run: sudo apt install -y libcurl4-openssl-dev libsqlite3-dev pkg-config git curl ldc
|
||||
run: sudo apt install -y libcurl4-openssl-dev libsqlite3-dev pkg-config git curl ldc libdbus-1-dev
|
||||
|
||||
- name: Configure
|
||||
run: ./configure
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ NOTIFICATIONS = @NOTIFICATIONS@
|
|||
HAVE_SYSTEMD = @HAVE_SYSTEMD@
|
||||
systemduserunitdir = @systemduserunitdir@
|
||||
systemdsystemunitdir = @systemdsystemunitdir@
|
||||
all_libs = @curl_LIBS@ @sqlite_LIBS@ @notify_LIBS@ @bsd_inotify_LIBS@ @dynamic_linker_LIBS@
|
||||
all_libs = @curl_LIBS@ @sqlite_LIBS@ @dbus_LIBS@ @notify_LIBS@ @bsd_inotify_LIBS@ @dynamic_linker_LIBS@
|
||||
COMPLETIONS = @COMPLETIONS@
|
||||
BASH_COMPLETION_DIR = @BASH_COMPLETION_DIR@
|
||||
ZSH_COMPLETION_DIR = @ZSH_COMPLETION_DIR@
|
||||
|
|
@ -77,7 +77,8 @@ SOURCES = \
|
|||
src/clientSideFiltering.d \
|
||||
src/monitor.d \
|
||||
src/arsd/cgi.d \
|
||||
src/xattr.d
|
||||
src/xattr.d \
|
||||
src/intune.d
|
||||
|
||||
ifeq ($(NOTIFICATIONS),yes)
|
||||
SOURCES += src/notifications/notify.d src/notifications/dnotify.d
|
||||
|
|
|
|||
3
config
3
config
|
|
@ -178,6 +178,9 @@
|
|||
## Only upload changes to OneDrive, do not download from cloud.
|
||||
#upload_only = "false"
|
||||
|
||||
## Single Sign-On (SSO) via Intune using the Microsoft Identity Device Broker
|
||||
#use_intune_sso = "true"
|
||||
|
||||
## This configuration option controls the application function to move online deleted files to a 'Recycle Bin' on your system.
|
||||
#use_recycle_bin = "false"
|
||||
|
||||
|
|
|
|||
119
configure
vendored
119
configure
vendored
|
|
@ -601,6 +601,9 @@ notify_CFLAGS
|
|||
HAVE_SYSTEMD
|
||||
systemduserunitdir
|
||||
systemdsystemunitdir
|
||||
enable_dbus
|
||||
dbus_LIBS
|
||||
dbus_CFLAGS
|
||||
sqlite_LIBS
|
||||
sqlite_CFLAGS
|
||||
curl_LIBS
|
||||
|
|
@ -639,6 +642,7 @@ infodir
|
|||
docdir
|
||||
oldincludedir
|
||||
includedir
|
||||
runstatedir
|
||||
localstatedir
|
||||
sharedstatedir
|
||||
sysconfdir
|
||||
|
|
@ -683,6 +687,8 @@ curl_CFLAGS
|
|||
curl_LIBS
|
||||
sqlite_CFLAGS
|
||||
sqlite_LIBS
|
||||
dbus_CFLAGS
|
||||
dbus_LIBS
|
||||
notify_CFLAGS
|
||||
notify_LIBS
|
||||
bashcompdir'
|
||||
|
|
@ -724,6 +730,7 @@ datadir='${datarootdir}'
|
|||
sysconfdir='${prefix}/etc'
|
||||
sharedstatedir='${prefix}/com'
|
||||
localstatedir='${prefix}/var'
|
||||
runstatedir='${localstatedir}/run'
|
||||
includedir='${prefix}/include'
|
||||
oldincludedir='/usr/include'
|
||||
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
|
||||
|
|
@ -976,6 +983,15 @@ do
|
|||
| -silent | --silent | --silen | --sile | --sil)
|
||||
silent=yes ;;
|
||||
|
||||
-runstatedir | --runstatedir | --runstatedi | --runstated \
|
||||
| --runstate | --runstat | --runsta | --runst | --runs \
|
||||
| --run | --ru | --r)
|
||||
ac_prev=runstatedir ;;
|
||||
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
|
||||
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
|
||||
| --run=* | --ru=* | --r=*)
|
||||
runstatedir=$ac_optarg ;;
|
||||
|
||||
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
|
||||
ac_prev=sbindir ;;
|
||||
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
|
||||
|
|
@ -1113,7 +1129,7 @@ fi
|
|||
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
|
||||
datadir sysconfdir sharedstatedir localstatedir includedir \
|
||||
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
|
||||
libdir localedir mandir
|
||||
libdir localedir mandir runstatedir
|
||||
do
|
||||
eval ac_val=\$$ac_var
|
||||
# Remove trailing slashes.
|
||||
|
|
@ -1266,6 +1282,7 @@ Fine tuning of the installation directories:
|
|||
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
|
||||
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
|
||||
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
|
||||
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
|
||||
--libdir=DIR object code libraries [EPREFIX/lib]
|
||||
--includedir=DIR C header files [PREFIX/include]
|
||||
--oldincludedir=DIR C header files for non-gcc [/usr/include]
|
||||
|
|
@ -1328,6 +1345,8 @@ Some influential environment variables:
|
|||
sqlite_CFLAGS
|
||||
C compiler flags for sqlite, overriding pkg-config
|
||||
sqlite_LIBS linker flags for sqlite, overriding pkg-config
|
||||
dbus_CFLAGS C compiler flags for dbus, overriding pkg-config
|
||||
dbus_LIBS linker flags for dbus, overriding pkg-config
|
||||
notify_CFLAGS
|
||||
C compiler flags for notify, overriding pkg-config
|
||||
notify_LIBS linker flags for notify, overriding pkg-config
|
||||
|
|
@ -2014,7 +2033,7 @@ $as_echo "no" >&6; }
|
|||
fi
|
||||
fi
|
||||
|
||||
PACKAGE_DATE="April 2025"
|
||||
PACKAGE_DATE="May 2025"
|
||||
|
||||
|
||||
for ac_prog in dmd ldmd2 ldc2 gdmd gdc
|
||||
|
|
@ -2402,6 +2421,101 @@ $as_echo "yes" >&6; }
|
|||
|
||||
fi
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable dbus support" >&5
|
||||
$as_echo_n "checking whether to enable dbus support... " >&6; }
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
enable_dbus=yes
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes (on Linux)" >&5
|
||||
$as_echo "yes (on Linux)" >&6; }
|
||||
|
||||
pkg_failed=no
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for dbus" >&5
|
||||
$as_echo_n "checking for dbus... " >&6; }
|
||||
|
||||
if test -n "$dbus_CFLAGS"; then
|
||||
pkg_cv_dbus_CFLAGS="$dbus_CFLAGS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.0\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.0") 2>&5
|
||||
ac_status=$?
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_dbus_CFLAGS=`$PKG_CONFIG --cflags "dbus-1 >= 1.0" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
fi
|
||||
else
|
||||
pkg_failed=untried
|
||||
fi
|
||||
if test -n "$dbus_LIBS"; then
|
||||
pkg_cv_dbus_LIBS="$dbus_LIBS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.0\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.0") 2>&5
|
||||
ac_status=$?
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_dbus_LIBS=`$PKG_CONFIG --libs "dbus-1 >= 1.0" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
fi
|
||||
else
|
||||
pkg_failed=untried
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if test $pkg_failed = yes; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
|
||||
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
|
||||
_pkg_short_errors_supported=yes
|
||||
else
|
||||
_pkg_short_errors_supported=no
|
||||
fi
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
dbus_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 >= 1.0" 2>&1`
|
||||
else
|
||||
dbus_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 >= 1.0" 2>&1`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$dbus_PKG_ERRORS" >&5
|
||||
|
||||
as_fn_error $? "dbus-1 development files not found. Please install dbus-devel (Red Hat), libdbus-1-dev (Debian) or dbus (Arch | Manjaro)" "$LINENO" 5
|
||||
|
||||
elif test $pkg_failed = untried; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
as_fn_error $? "dbus-1 development files not found. Please install dbus-devel (Red Hat), libdbus-1-dev (Debian) or dbus (Arch | Manjaro)" "$LINENO" 5
|
||||
|
||||
else
|
||||
dbus_CFLAGS=$pkg_cv_dbus_CFLAGS
|
||||
dbus_LIBS=$pkg_cv_dbus_LIBS
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
|
||||
$as_echo "#define HAVE_DBUS 1" >>confdefs.h
|
||||
|
||||
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
enable_dbus=no
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no (not on Linux)" >&5
|
||||
$as_echo "no (not on Linux)" >&6; }
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Check whether --with-systemdsystemunitdir was given.
|
||||
if test "${with_systemdsystemunitdir+set}" = set; then :
|
||||
|
|
@ -2596,6 +2710,7 @@ esac
|
|||
|
||||
|
||||
|
||||
|
||||
# Check whether --enable-completions was given.
|
||||
if test "${enable_completions+set}" = set; then :
|
||||
enableval=$enable_completions;
|
||||
|
|
|
|||
26
configure.ac
26
configure.ac
|
|
@ -21,7 +21,7 @@ dnl necessary programs: install, pkg-config
|
|||
AC_PROG_INSTALL
|
||||
PKG_PROG_PKG_CONFIG
|
||||
|
||||
PACKAGE_DATE="April 2025"
|
||||
PACKAGE_DATE="May 2025"
|
||||
AC_SUBST([PACKAGE_DATE])
|
||||
|
||||
dnl Determine D compiler
|
||||
|
|
@ -185,10 +185,31 @@ AC_SUBST([LINKER_DCFLAG])
|
|||
AC_SUBST([OUTPUT_DCFLAG])
|
||||
AC_SUBST([WERROR_DCFLAG])
|
||||
|
||||
dnl Check for required modules: curl and sqlite at the moment
|
||||
dnl Check for required modules: curl, sqlite and dbus if required
|
||||
PKG_CHECK_MODULES([curl],[libcurl])
|
||||
PKG_CHECK_MODULES([sqlite],[sqlite3])
|
||||
|
||||
AC_MSG_CHECKING([whether to enable dbus support])
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
enable_dbus=yes
|
||||
AC_MSG_RESULT([yes (on Linux)])
|
||||
PKG_CHECK_MODULES([dbus], [dbus-1 >= 1.0],
|
||||
[AC_DEFINE([HAVE_DBUS], [1], [Define if you have dbus-1])]
|
||||
,
|
||||
[AC_MSG_ERROR([dbus-1 development files not found. Please install dbus-devel (Red Hat), libdbus-1-dev (Debian) or dbus (Arch | Manjaro)])]
|
||||
)
|
||||
;;
|
||||
*)
|
||||
enable_dbus=no
|
||||
AC_MSG_RESULT([no (not on Linux)])
|
||||
;;
|
||||
esac
|
||||
|
||||
AC_SUBST([enable_dbus])
|
||||
|
||||
|
||||
|
||||
dnl
|
||||
dnl systemd and unit file directories
|
||||
dnl This is a bit tricky, because we want to allow for
|
||||
|
|
@ -304,6 +325,7 @@ esac
|
|||
|
||||
AC_SUBST([dynamic_linker_LIBS])
|
||||
|
||||
|
||||
dnl
|
||||
dnl Completion support
|
||||
dnl First determine whether completions are requested, pass that to Makefile
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RUN go install -ldflags "-s -w" github.com/tianon/gosu@${GOSU_VERSION}
|
|||
|
||||
FROM fedora:${FEDORA_VERSION} AS builder-onedrive
|
||||
|
||||
RUN dnf install -y ldc pkgconf libcurl-devel sqlite-devel git awk
|
||||
RUN dnf install -y ldc pkgconf libcurl-devel sqlite-devel dbus-devel git awk
|
||||
|
||||
ENV PKG_CONFIG=/usr/bin/pkgconf
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RUN go install -ldflags "-s -w" github.com/tianon/gosu@${GOSU_VERSION}
|
|||
|
||||
FROM alpine:${ALPINE_VERSION} AS builder-onedrive
|
||||
|
||||
RUN apk add --update --no-cache alpine-sdk gnupg xz curl-dev sqlite-dev binutils-gold autoconf automake ldc
|
||||
RUN apk add --update --no-cache alpine-sdk gnupg xz curl-dev sqlite-dev dbus-dev binutils-gold autoconf automake ldc
|
||||
|
||||
COPY . /usr/src/onedrive
|
||||
WORKDIR /usr/src/onedrive
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RUN apt-get clean \
|
|||
&& apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential curl ca-certificates libcurl4-openssl-dev libsqlite3-dev libxml2-dev pkg-config git ldc \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential curl ca-certificates libcurl4-openssl-dev libsqlite3-dev libxml2-dev libdbus-1-dev pkg-config git ldc \
|
||||
# Install|update curl from backports
|
||||
&& apt-get install -t bookworm-backports -y curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ Before reading this document, please ensure you are running application version
|
|||
- [threads](#threads)
|
||||
- [transfer_order](#transfer_order)
|
||||
- [upload_only](#upload_only)
|
||||
- [use_intune_sso](#use_intune_sso)
|
||||
- [use_recycle_bin](#use_recycle_bin)
|
||||
- [user_agent](#user_agent)
|
||||
- [webhook_enabled](#webhook_enabled)
|
||||
|
|
@ -1017,6 +1018,20 @@ _**CLI Option Use:**_ `--upload-only`
|
|||
> [!IMPORTANT]
|
||||
> To ensure that data deleted locally remains accessible online, you can use the 'no_remote_delete' option. If you want to delete the data from your local storage after a successful upload to Microsoft OneDrive, you can use the 'remove_source_files' option.
|
||||
|
||||
### use_intune_sso
|
||||
_**Description:**_ Enable this option to authenticate using Intune Single Sign-On (SSO) via the Microsoft Identity Device Broker over D-Bus. This method is suitable for environments where the system is Intune-enrolled and allows seamless token retrieval without requiring browser interaction.
|
||||
|
||||
_**Value Type:**_ Boolean
|
||||
|
||||
_**Default Value:**_ False
|
||||
|
||||
_**Config Example:**_ `use_intune_sso = "false"` or `use_intune_sso = "true"`
|
||||
|
||||
_**CLI Option Use:**_ *None - this is a config file option only*
|
||||
|
||||
> [!NOTE]
|
||||
> The installation and configuration of Intune for your platform is beyond the scope of this documentation.
|
||||
|
||||
### use_recycle_bin
|
||||
_**Description:**_ This configuration option controls the application function to move online deleted files to a 'Recycle Bin' on your system. This allows you to review online deleted data manually before this is purged from your actual system.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ As stated above, you will need at least GDC version 15. If your distribution's r
|
|||
|
||||
### Dependencies: Arch Linux & Manjaro Linux
|
||||
```text
|
||||
sudo pacman -S git make pkg-config curl sqlite ldc
|
||||
sudo pacman -S git make pkg-config curl sqlite dbus ldc
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
```text
|
||||
|
|
@ -76,7 +76,7 @@ CentOS 7.x and RHEL 7.x reached End of Life status on June 30th 2024 and is no l
|
|||
### Dependencies: Fedora > Version 18 / CentOS 8.x / CentOS 9.x / RHEL 8.x / RHEL 9.x
|
||||
```text
|
||||
sudo dnf groupinstall 'Development Tools'
|
||||
sudo dnf install libcurl-devel sqlite-devel
|
||||
sudo dnf install libcurl-devel sqlite-devel dbus-devel
|
||||
curl -fsS https://dlang.org/install.sh | bash -s dmd
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
|
|
@ -87,7 +87,7 @@ sudo dnf install libnotify-devel
|
|||
Fedora > 41 uses dnf5 which removes some deprecated aliases, specifically 'groupinstall' in this instance.
|
||||
```text
|
||||
sudo dnf group install development-tools
|
||||
sudo dnf install libcurl-devel sqlite-devel
|
||||
sudo dnf install libcurl-devel sqlite-devel dbus-devel
|
||||
```
|
||||
Before running the dmd install you need to check for the option 'use-keyboxd' in your gnupg common.conf file and comment it out while running the install.
|
||||
```text
|
||||
|
|
@ -144,7 +144,7 @@ These dependencies are also applicable for all Ubuntu based distributions such a
|
|||
* Peppermint OS
|
||||
```text
|
||||
sudo apt install build-essential
|
||||
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl systemd-dev
|
||||
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl systemd-dev libdbus-1-dev
|
||||
curl -fsS https://dlang.org/install.sh | bash -s dmd
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
|
|
@ -166,7 +166,7 @@ These instructions were validated using:
|
|||
|
||||
```text
|
||||
sudo apt install build-essential
|
||||
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl ldc systemd-dev
|
||||
sudo apt install libcurl4-openssl-dev libsqlite3-dev pkg-config git curl ldc systemd-dev libdbus-1-dev
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
```text
|
||||
|
|
@ -177,7 +177,7 @@ sudo apt install libnotify-dev
|
|||
```text
|
||||
sudo zypper addrepo https://download.opensuse.org/repositories/devel:languages:D/openSUSE_Leap_15.0/devel:languages:D.repo
|
||||
sudo zypper refresh
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static dbus-1-devel
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
```text
|
||||
|
|
@ -188,7 +188,7 @@ sudo zypper install libnotify-devel
|
|||
```text
|
||||
sudo zypper addrepo https://download.opensuse.org/repositories/devel:languages:D/openSUSE_Leap_15.1/devel:languages:D.repo
|
||||
sudo zypper refresh
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static dbus-1-devel
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
```text
|
||||
|
|
@ -198,7 +198,7 @@ sudo zypper install libnotify-devel
|
|||
### Dependencies: OpenSuSE Leap 15.2
|
||||
```text
|
||||
sudo zypper refresh
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static
|
||||
sudo zypper install gcc git libcurl-devel sqlite3-devel dmd phobos-devel phobos-devel-static dbus-1-devel
|
||||
```
|
||||
For GUI notifications the following is also necessary:
|
||||
```text
|
||||
|
|
|
|||
|
|
@ -341,6 +341,11 @@ Once you've installed the application, you'll need to authorise it using your Mi
|
|||
|
||||
Please be aware that some companies may require you to explicitly add this app to the [Microsoft MyApps portal](https://myapps.microsoft.com/). To add an approved app to your apps, click on the ellipsis in the top-right corner and select "Request new apps." On the next page, you can add this app. If it's not listed, you should make a request through your IT department.
|
||||
|
||||
This client supports the following methods to authenticate the application with Microsoft OneDrive:
|
||||
* Supports interactive browser-based authentication using OAuth2 and a response URI
|
||||
* Supports seamless Single Sign-On (SSO) via Intune using the Microsoft Identity Device Broker D-Bus interface
|
||||
|
||||
#### Interactive Authentication using OAuth2 and a response URI
|
||||
When you run the application for the first time, you'll be prompted to open a specific URL using your web browser, where you'll need to log in to your Microsoft Account and grant the application permission to access your files. After granting permission to the application, you'll be redirected to a blank page. Simply copy the URI from the blank page and paste it into the application.
|
||||
|
||||
**Example:**
|
||||
|
|
@ -348,7 +353,7 @@ When you run the application for the first time, you'll be prompted to open a sp
|
|||
[user@hostname ~]$ onedrive
|
||||
Authorise this app by visiting:
|
||||
|
||||
https://login.microsoftonline.com/common/oauth2/v2.0/authorise?client_id=22c49a0d-d21c-4792-aed1-8f163c982546&scope=Files.ReadWrite%20Files.ReadWrite.all%20Sites.ReadWrite.All%20offline_access&response_type=code&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||
https://login.microsoftonline.com/common/oauth2/v2.0/authorise?client_id=d50ca740-c83f-4d1b-b616-12c519384f0c&scope=Files.ReadWrite%20Files.ReadWrite.all%20Sites.ReadWrite.All%20offline_access&response_type=code&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||
|
||||
Enter the response URI from your browser: https://login.microsoftonline.com/common/oauth2/nativeclient?code=<redacted>
|
||||
|
||||
|
|
@ -360,6 +365,22 @@ Please use 'onedrive --help' for further assistance on how to run this applicati
|
|||
> [!IMPORTANT]
|
||||
> Without additional input or configuration, the OneDrive Client for Linux will automatically adhere to default application settings during synchronisation processes with Microsoft OneDrive.
|
||||
|
||||
#### Single Sign-On (SSO) via Intune using the Microsoft Identity Device Broker
|
||||
To use this method of authentication, you must add the following configuration to your 'config' file:
|
||||
```
|
||||
use_intune_sso = "true"
|
||||
```
|
||||
The application will check to ensure that Intune is operational and that the required dbus elements are available. Should these be available, the following will be displayed:
|
||||
```
|
||||
...
|
||||
Client has been configured to use Intune SSO via Microsoft Identity Broker dbus session - checking usage criteria
|
||||
Intune SSO via Microsoft Identity Broker dbus session usage criteria met - will attempt to authenticate via Intune
|
||||
...
|
||||
```
|
||||
> [!NOTE]
|
||||
> The installation and configuration of Intune for your platform is beyond the scope of this documentation.
|
||||
|
||||
|
||||
### Display Your Applicable Runtime Configuration
|
||||
To verify the configuration that the application will use, use the following command:
|
||||
```text
|
||||
|
|
|
|||
32
readme.md
32
readme.md
|
|
@ -14,21 +14,23 @@ Originally derived as a 'fork' from the [skilion](https://github.com/skilion/one
|
|||
This client represents a 100% re-imagining of the original work, addressing numerous notable bugs and issues while incorporating a significant array of new features. This client has been under active development since mid-2018.
|
||||
|
||||
## Features
|
||||
* Compatible with OneDrive Personal, OneDrive for Business including accessing Microsoft SharePoint Libraries
|
||||
* Provides rules for client-side filtering to select data for syncing with Microsoft OneDrive accounts
|
||||
* Caches sync state for efficiency
|
||||
* Supports a dry-run option for safe configuration testing
|
||||
* Validates file transfers to ensure data integrity
|
||||
* Monitors local files in real-time using inotify
|
||||
* Supports interrupted uploads for completion at a later time
|
||||
* Capability to sync remote updates immediately via webhooks
|
||||
* Enhanced synchronisation speed with multi-threaded file transfers
|
||||
* Manages traffic bandwidth use with rate limiting
|
||||
* Supports seamless access to shared folders and files across both OneDrive Personal and OneDrive for Business accounts
|
||||
* Supports national cloud deployments including Microsoft Cloud for US Government, Microsoft Cloud Germany and Azure and Office 365 operated by VNET in China
|
||||
* Supports sending desktop alerts using libnotify
|
||||
* Protects against significant data loss on OneDrive after configuration changes
|
||||
* Works with both single and multi-tenant applications
|
||||
* Compatible with OneDrive Personal and OneDrive for Business, including access to Microsoft SharePoint Libraries
|
||||
* Supports seamless access to shared folders and files across both OneDrive Personal and OneDrive for Business accounts
|
||||
* Supports single-tenant and multi-tenant applications
|
||||
* Supports Intune Single Sign-On (SSO) authentication via the Microsoft Identity Device Broker (D-Bus interface)
|
||||
* Supports national cloud deployments including Microsoft Cloud for US Government, Microsoft Cloud Germany, and Azure/Office 365 operated by VNET in China
|
||||
* Provides rules for client-side filtering to select data for syncing with Microsoft OneDrive accounts
|
||||
* Protects against significant data loss on OneDrive after configuration changes
|
||||
* Supports a dry-run option for safe configuration testing
|
||||
* Validates file transfers to ensure data integrity
|
||||
* Caches sync state for efficiency
|
||||
* Monitors local files in real-time using inotify
|
||||
* Capability to sync remote updates immediately via webhooks
|
||||
* Supports interrupted uploads for completion at a later time
|
||||
* Enhanced synchronisation speed with multi-threaded file transfers
|
||||
* Manages traffic bandwidth use with rate limiting
|
||||
* Supports sending desktop alerts using libnotify
|
||||
|
||||
|
||||
## What's missing
|
||||
* Ability to encrypt/decrypt files on-the-fly when uploading/downloading files from OneDrive
|
||||
|
|
|
|||
43
src/config.d
43
src/config.d
|
|
@ -50,6 +50,7 @@ class ApplicationConfig {
|
|||
// Microsoft Requirements
|
||||
// - Default Application ID (abraunegg)
|
||||
immutable string defaultApplicationId = "d50ca740-c83f-4d1b-b616-12c519384f0c";
|
||||
|
||||
// - Microsoft User Agent ISV Tag
|
||||
immutable string isvTag = "ISV";
|
||||
// - Microsoft User Agent Company name
|
||||
|
|
@ -119,6 +120,10 @@ class ApplicationConfig {
|
|||
SysTime accessTokenExpiration;
|
||||
// Store the 'session_upload.CRC32-HASH' file path
|
||||
string uploadSessionFilePath = "";
|
||||
// Store the Intune account information
|
||||
string intuneAccountDetails;
|
||||
// Store the Intune account information on disk for reuse
|
||||
string intuneAccountDetailsFilePath = "";
|
||||
|
||||
// API initialisation flags
|
||||
bool apiWasInitialised = false;
|
||||
|
|
@ -412,6 +417,9 @@ class ApplicationConfig {
|
|||
// Diable setting the permissions for directories and files, using the inherited permissions
|
||||
boolValues["disable_permission_set"] = false;
|
||||
|
||||
// Use authentication via Intune SSO via Microsoft Identity Broker (microsoft-identity-broker) dbus session
|
||||
boolValues["use_intune_sso"] = false;
|
||||
|
||||
// EXPAND USERS HOME DIRECTORY
|
||||
// Determine the users home directory.
|
||||
// Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
|
||||
|
|
@ -535,6 +543,8 @@ class ApplicationConfig {
|
|||
// Update application set variables based on configDirName
|
||||
// - What is the full path for the 'refresh_token'
|
||||
refreshTokenFilePath = buildNormalizedPath(buildPath(configDirName, "refresh_token"));
|
||||
// - What is the full path for the 'intune_account'
|
||||
intuneAccountDetailsFilePath = buildNormalizedPath(buildPath(configDirName, "intune_account"));
|
||||
// - What is the full path for the 'delta_link'
|
||||
deltaLinkFilePath = buildNormalizedPath(buildPath(configDirName, "delta_link"));
|
||||
// - What is the full path for the 'items.sqlite3' - the database cache file
|
||||
|
|
@ -561,17 +571,18 @@ class ApplicationConfig {
|
|||
|
||||
// Debug Output for application set variables based on configDirName
|
||||
if (debugLogging) {
|
||||
addLogEntry("refreshTokenFilePath = " ~ refreshTokenFilePath, ["debug"]);
|
||||
addLogEntry("deltaLinkFilePath = " ~ deltaLinkFilePath, ["debug"]);
|
||||
addLogEntry("databaseFilePath = " ~ databaseFilePath, ["debug"]);
|
||||
addLogEntry("databaseFilePathDryRun = " ~ databaseFilePathDryRun, ["debug"]);
|
||||
addLogEntry("uploadSessionFilePath = " ~ uploadSessionFilePath, ["debug"]);
|
||||
addLogEntry("userConfigFilePath = " ~ userConfigFilePath, ["debug"]);
|
||||
addLogEntry("syncListFilePath = " ~ syncListFilePath, ["debug"]);
|
||||
addLogEntry("systemConfigFilePath = " ~ systemConfigFilePath, ["debug"]);
|
||||
addLogEntry("configBackupFile = " ~ configBackupFile, ["debug"]);
|
||||
addLogEntry("configHashFile = " ~ configHashFile, ["debug"]);
|
||||
addLogEntry("syncListHashFile = " ~ syncListHashFile, ["debug"]);
|
||||
addLogEntry("refreshTokenFilePath = " ~ refreshTokenFilePath, ["debug"]);
|
||||
addLogEntry("intuneAccountDetailsFilePath = " ~ intuneAccountDetailsFilePath, ["debug"]);
|
||||
addLogEntry("deltaLinkFilePath = " ~ deltaLinkFilePath, ["debug"]);
|
||||
addLogEntry("databaseFilePath = " ~ databaseFilePath, ["debug"]);
|
||||
addLogEntry("databaseFilePathDryRun = " ~ databaseFilePathDryRun, ["debug"]);
|
||||
addLogEntry("uploadSessionFilePath = " ~ uploadSessionFilePath, ["debug"]);
|
||||
addLogEntry("userConfigFilePath = " ~ userConfigFilePath, ["debug"]);
|
||||
addLogEntry("syncListFilePath = " ~ syncListFilePath, ["debug"]);
|
||||
addLogEntry("systemConfigFilePath = " ~ systemConfigFilePath, ["debug"]);
|
||||
addLogEntry("configBackupFile = " ~ configBackupFile, ["debug"]);
|
||||
addLogEntry("configHashFile = " ~ configHashFile, ["debug"]);
|
||||
addLogEntry("syncListHashFile = " ~ syncListHashFile, ["debug"]);
|
||||
}
|
||||
|
||||
// Configure the Hash and Backup File Permission Value
|
||||
|
|
@ -773,6 +784,13 @@ class ApplicationConfig {
|
|||
return configuredFilePermissionMode;
|
||||
}
|
||||
|
||||
// Set file permissions for 'refresh_token' and 'intune_account' to 0600
|
||||
int returnSecureFilePermission() {
|
||||
string valueToConvert = to!string(defaultFilePermissionMode);
|
||||
auto convertedValue = parse!long(valueToConvert, 8);
|
||||
return to!int(convertedValue);
|
||||
}
|
||||
|
||||
// Load a configuration file from the provided filename
|
||||
private bool loadConfigFile(string filename) {
|
||||
try {
|
||||
|
|
@ -1537,6 +1555,9 @@ class ApplicationConfig {
|
|||
// Config Options as per 'config' file
|
||||
addLogEntry("Config option 'sync_dir' = " ~ getValueString("sync_dir"));
|
||||
|
||||
// authentication
|
||||
addLogEntry("Config option 'use_intune_sso' = " ~ to!string(getValueBool("use_intune_sso")));
|
||||
|
||||
// logging and notifications
|
||||
addLogEntry("Config option 'enable_logging' = " ~ to!string(getValueBool("enable_logging")));
|
||||
addLogEntry("Config option 'log_dir' = " ~ getValueString("log_dir"));
|
||||
|
|
|
|||
254
src/intune.d
Normal file
254
src/intune.d
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// What is this module called?
|
||||
module intune;
|
||||
|
||||
// What does this module require to function?
|
||||
import core.stdc.string : strcmp;
|
||||
import core.stdc.stdlib : malloc, free;
|
||||
import core.thread : Thread;
|
||||
import core.time : dur;
|
||||
import std.string : fromStringz, toStringz;
|
||||
import std.conv : to;
|
||||
import std.json : JSONValue, parseJSON, JSONType;
|
||||
import std.uuid : randomUUID;
|
||||
import std.range : empty;
|
||||
import std.format : format;
|
||||
|
||||
// What 'onedrive' modules do we import?
|
||||
import log;
|
||||
|
||||
extern(C):
|
||||
alias dbus_bool_t = int;
|
||||
|
||||
struct DBusError {
|
||||
char* name;
|
||||
char* message;
|
||||
uint[8] dummy;
|
||||
void* padding;
|
||||
}
|
||||
|
||||
struct DBusConnection;
|
||||
struct DBusMessage;
|
||||
struct DBusMessageIter;
|
||||
|
||||
enum DBusBusType {
|
||||
DBUS_BUS_SESSION = 0,
|
||||
}
|
||||
|
||||
void dbus_error_init(DBusError* error);
|
||||
void dbus_error_free(DBusError* error);
|
||||
int dbus_error_is_set(DBusError* error);
|
||||
DBusConnection* dbus_bus_get(DBusBusType type, DBusError* error);
|
||||
dbus_bool_t dbus_bus_name_has_owner(DBusConnection* conn, const char* name, DBusError* error);
|
||||
DBusMessage* dbus_message_new_method_call(const char* dest, const char* path, const char* iface, const char* method);
|
||||
dbus_bool_t dbus_connection_send(DBusConnection* conn, DBusMessage* msg, void* client_serial);
|
||||
void dbus_connection_flush(DBusConnection* conn);
|
||||
DBusMessage* dbus_connection_send_with_reply_and_block(DBusConnection* conn, DBusMessage* msg, int timeout_ms, DBusError* error);
|
||||
void dbus_message_unref(DBusMessage* msg);
|
||||
dbus_bool_t dbus_message_iter_init_append(DBusMessage* msg, DBusMessageIter* iter);
|
||||
dbus_bool_t dbus_message_iter_append_basic(DBusMessageIter* iter, int type, const void* value);
|
||||
dbus_bool_t dbus_message_iter_init(DBusMessage* msg, DBusMessageIter* iter);
|
||||
dbus_bool_t dbus_message_iter_get_arg_type(DBusMessageIter* iter);
|
||||
void dbus_message_iter_get_basic(DBusMessageIter* iter, void* value);
|
||||
|
||||
enum DBUS_TYPE_STRING = 115;
|
||||
enum DBUS_MESSAGE_ITER_SIZE = 128;
|
||||
|
||||
bool check_intune_broker_available() {
|
||||
version (linux) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
DBusConnection* conn = dbus_bus_get(DBusBusType.DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
dbus_error_free(&err);
|
||||
return false;
|
||||
}
|
||||
if (conn is null) return false;
|
||||
dbus_bool_t hasOwner = dbus_bus_name_has_owner(conn, "com.microsoft.identity.broker1", &err);
|
||||
if (dbus_error_is_set(&err)) {
|
||||
dbus_error_free(&err);
|
||||
return false;
|
||||
}
|
||||
return hasOwner != 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool wait_for_broker(int timeoutSeconds = 10) {
|
||||
int waited = 0;
|
||||
while (waited < timeoutSeconds) {
|
||||
if (check_intune_broker_available()) return true;
|
||||
Thread.sleep(dur!"seconds"(1));
|
||||
waited++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string build_auth_request(string accountJson = "", string clientId) {
|
||||
string header = format(`{
|
||||
"authParameters": {
|
||||
"clientId": "%s",
|
||||
"redirectUri": "https://login.microsoftonline.com/common/oauth2/nativeclient",
|
||||
"authority": "https://login.microsoftonline.com/common",
|
||||
"requestedScopes": [
|
||||
"Files.ReadWrite",
|
||||
"Files.ReadWrite.All",
|
||||
"Sites.ReadWrite.All",
|
||||
"offline_access"
|
||||
]`, clientId);
|
||||
|
||||
string footer = `
|
||||
}
|
||||
}`;
|
||||
|
||||
if (!accountJson.empty)
|
||||
return header ~ `,"account": ` ~ accountJson ~ footer;
|
||||
else
|
||||
return header ~ footer;
|
||||
}
|
||||
|
||||
struct AuthResult {
|
||||
JSONValue brokerTokenResponse;
|
||||
}
|
||||
|
||||
// Initiate interactive authentication via D-Bus using the Microsoft Identity Broker
|
||||
AuthResult acquire_token_interactive(string clientId) {
|
||||
AuthResult result;
|
||||
|
||||
version (linux) {
|
||||
if (!wait_for_broker(10)) {
|
||||
addLogEntry("Timed out waiting for Identity Broker to appear on D-Bus");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Step 1: Call acquireTokenInteractively and capture account from result
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
DBusConnection* conn = dbus_bus_get(DBusBusType.DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err) || conn is null) return result;
|
||||
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
"com.microsoft.identity.broker1",
|
||||
"/com/microsoft/identity/broker1",
|
||||
"com.microsoft.identity.Broker1",
|
||||
"acquireTokenInteractively"
|
||||
);
|
||||
if (msg is null) return result;
|
||||
|
||||
string correlationId = randomUUID().toString();
|
||||
string accountJson = "";
|
||||
string requestJson = build_auth_request(accountJson, clientId);
|
||||
|
||||
DBusMessageIter* args = cast(DBusMessageIter*) malloc(DBUS_MESSAGE_ITER_SIZE);
|
||||
if (!dbus_message_iter_init_append(msg, args)) {
|
||||
dbus_message_unref(msg); free(args); return result;
|
||||
}
|
||||
|
||||
const(char)* protocol = toStringz("0.0");
|
||||
const(char)* corrId = toStringz(correlationId);
|
||||
const(char)* reqJson = toStringz(requestJson);
|
||||
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &protocol);
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &corrId);
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &reqJson);
|
||||
free(args);
|
||||
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn, msg, 60000, &err);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
if (dbus_error_is_set(&err) || reply is null) {
|
||||
addLogEntry("Interactive call failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
DBusMessageIter* iter = cast(DBusMessageIter*) malloc(DBUS_MESSAGE_ITER_SIZE);
|
||||
if (!dbus_message_iter_init(reply, iter)) {
|
||||
dbus_message_unref(reply); free(iter); return result;
|
||||
}
|
||||
|
||||
char* responseStr;
|
||||
dbus_message_iter_get_basic(iter, &responseStr);
|
||||
dbus_message_unref(reply); free(iter);
|
||||
|
||||
string jsonResponse = fromStringz(responseStr).idup;
|
||||
if (debugLogging) {addLogEntry("Interactive raw response: " ~ to!string(jsonResponse), ["debug"]);}
|
||||
|
||||
JSONValue parsed = parseJSON(jsonResponse);
|
||||
if (parsed.type != JSONType.object) return result;
|
||||
|
||||
auto obj = parsed.object;
|
||||
if ("brokerTokenResponse" in obj) {
|
||||
result.brokerTokenResponse = obj["brokerTokenResponse"];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Perform silent authentication via D-Bus using the Microsoft Identity Broker
|
||||
AuthResult acquire_token_silently(string accountJson, string clientId) {
|
||||
AuthResult result;
|
||||
|
||||
version (linux) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
DBusConnection* conn = dbus_bus_get(DBusBusType.DBUS_BUS_SESSION, &err);
|
||||
if (dbus_error_is_set(&err) || conn is null) return result;
|
||||
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
"com.microsoft.identity.broker1",
|
||||
"/com/microsoft/identity/broker1",
|
||||
"com.microsoft.identity.Broker1",
|
||||
"acquireTokenSilently"
|
||||
);
|
||||
if (msg is null) return result;
|
||||
|
||||
string correlationId = randomUUID().toString();
|
||||
string requestJson = build_auth_request(accountJson, clientId);
|
||||
|
||||
DBusMessageIter* args = cast(DBusMessageIter*) malloc(DBUS_MESSAGE_ITER_SIZE);
|
||||
if (!dbus_message_iter_init_append(msg, args)) {
|
||||
dbus_message_unref(msg);
|
||||
free(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
const(char)* protocol = toStringz("0.0");
|
||||
const(char)* corrId = toStringz(correlationId);
|
||||
const(char)* reqJson = toStringz(requestJson);
|
||||
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &protocol);
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &corrId);
|
||||
dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &reqJson);
|
||||
free(args);
|
||||
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn, msg, 10000, &err);
|
||||
dbus_message_unref(msg);
|
||||
if (dbus_error_is_set(&err) || reply is null) return result;
|
||||
|
||||
DBusMessageIter* iter = cast(DBusMessageIter*) malloc(DBUS_MESSAGE_ITER_SIZE);
|
||||
if (!dbus_message_iter_init(reply, iter)) {
|
||||
dbus_message_unref(reply);
|
||||
free(iter);
|
||||
return result;
|
||||
}
|
||||
|
||||
char* responseStr;
|
||||
dbus_message_iter_get_basic(iter, &responseStr);
|
||||
dbus_message_unref(reply);
|
||||
free(iter);
|
||||
|
||||
string jsonResponse = fromStringz(responseStr).idup;
|
||||
if (debugLogging) {addLogEntry("Silent raw response: " ~ to!string(jsonResponse), ["debug"]);}
|
||||
|
||||
JSONValue parsed = parseJSON(jsonResponse);
|
||||
if (parsed.type != JSONType.object) return result;
|
||||
|
||||
auto obj = parsed.object;
|
||||
if (!("brokerTokenResponse" in obj)) return result;
|
||||
|
||||
result.brokerTokenResponse = obj["brokerTokenResponse"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
30
src/main.d
30
src/main.d
|
|
@ -32,6 +32,7 @@ import itemdb;
|
|||
import clientSideFiltering;
|
||||
import monitor;
|
||||
import webhook;
|
||||
import intune;
|
||||
|
||||
// What other constant variables do we require?
|
||||
const int EXIT_RESYNC_REQUIRED = 126;
|
||||
|
|
@ -201,6 +202,29 @@ int main(string[] cliArgs) {
|
|||
syncOrMonitorMissing = true; // --sync or --monitor is missing
|
||||
}
|
||||
|
||||
// Has the client been configured to use Intune SSO via Microsoft Identity Broker (microsoft-identity-broker) dbus session
|
||||
// This is ONLY possible on Linux, not FreeBSD or other platforms
|
||||
version (linux) {
|
||||
if (appConfig.getValueBool("use_intune_sso")) {
|
||||
// The client is configured to use Intune SSO via Microsoft Identity Broker dbus session
|
||||
addLogEntry("Client has been configured to use Intune SSO via Microsoft Identity Broker dbus session - checking usage criteria");
|
||||
// We need to check that the available dbus is actually available
|
||||
if(wait_for_broker()) {
|
||||
// Usage criteria met, will attempt to use Intune SSO via dbus
|
||||
addLogEntry("Intune SSO via Microsoft Identity Broker dbus session usage criteria met - will attempt to authenticate via Intune");
|
||||
} else {
|
||||
// Microsoft Identity Broker dbus is not available
|
||||
addLogEntry();
|
||||
addLogEntry("Required Microsoft Identity Broker dbus capability not found - disabling authentication via Intune SSO");
|
||||
addLogEntry();
|
||||
appConfig.setValueBool("use_intune_sso" , false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure 'use_intune_sso' is disabled
|
||||
appConfig.setValueBool("use_intune_sso" , false);
|
||||
}
|
||||
|
||||
// Has the user configured to use the 'Recycle Bin' locally, for any files that are deleted online?
|
||||
if (appConfig.getValueBool("use_recycle_bin")) {
|
||||
// Configure the internal application paths which will be used to move rather than delete any online deletes to
|
||||
|
|
@ -437,7 +461,10 @@ int main(string[] cliArgs) {
|
|||
if (debugLogging) {addLogEntry("--logout requested", ["debug"]);}
|
||||
addLogEntry("Deleting the saved authentication status ...");
|
||||
if (!dryRun) {
|
||||
// Remove the 'refresh_token' file if present
|
||||
safeRemove(appConfig.refreshTokenFilePath);
|
||||
// Remove the 'intune_account' file if present
|
||||
safeRemove(appConfig.intuneAccountDetailsFilePath);
|
||||
} else {
|
||||
// --dry-run scenario ... technically we should not be making any local file changes .......
|
||||
addLogEntry("DRY RUN: Not removing the saved authentication status");
|
||||
|
|
@ -451,7 +478,10 @@ int main(string[] cliArgs) {
|
|||
if (debugLogging) {addLogEntry("--reauth requested", ["debug"]);}
|
||||
addLogEntry("Deleting the saved authentication status ... re-authentication requested");
|
||||
if (!dryRun) {
|
||||
// Remove the 'refresh_token' file if present
|
||||
safeRemove(appConfig.refreshTokenFilePath);
|
||||
// Remove the 'intune_account' file if present
|
||||
safeRemove(appConfig.intuneAccountDetailsFilePath);
|
||||
} else {
|
||||
// --dry-run scenario ... technically we should not be making any local file changes .......
|
||||
addLogEntry("DRY RUN: Not removing the saved authentication status");
|
||||
|
|
|
|||
442
src/onedrive.d
442
src/onedrive.d
|
|
@ -29,6 +29,7 @@ import config;
|
|||
import log;
|
||||
import util;
|
||||
import curlEngine;
|
||||
import intune;
|
||||
|
||||
// Define the 'OneDriveException' class
|
||||
class OneDriveException: Exception {
|
||||
|
|
@ -310,42 +311,55 @@ class OneDriveApi {
|
|||
}
|
||||
|
||||
// Has the application been authenticated?
|
||||
if (!exists(appConfig.refreshTokenFilePath)) {
|
||||
if (debugLogging) {addLogEntry("Application has no 'refresh_token' thus needs to be authenticated", ["debug"]);}
|
||||
authorised = authorise();
|
||||
} else {
|
||||
// Try and read the value from the appConfig if it is set, rather than trying to read the value from disk
|
||||
if (!appConfig.refreshToken.empty) {
|
||||
if (debugLogging) {addLogEntry("Read token from appConfig", ["debug"]);}
|
||||
refreshToken = strip(appConfig.refreshToken);
|
||||
authorised = true;
|
||||
// How do we authenticate - standard method or via Intune?
|
||||
if (appConfig.getValueBool("use_intune_sso")) {
|
||||
// Authenticate via Intune
|
||||
if (appConfig.accessToken.empty) {
|
||||
// No authentication via intune yet
|
||||
authorised = authorise();
|
||||
} else {
|
||||
// Try and read the file from disk
|
||||
try {
|
||||
refreshToken = strip(readText(appConfig.refreshTokenFilePath));
|
||||
// is the refresh_token empty?
|
||||
if (refreshToken.empty) {
|
||||
addLogEntry("RefreshToken exists but is empty: " ~ appConfig.refreshTokenFilePath);
|
||||
authorised = authorise();
|
||||
} else {
|
||||
// Existing token not empty
|
||||
authorised = true;
|
||||
// update appConfig.refreshToken
|
||||
appConfig.refreshToken = refreshToken;
|
||||
}
|
||||
} catch (FileException exception) {
|
||||
authorised = authorise();
|
||||
} catch (std.utf.UTFException exception) {
|
||||
// path contains characters which generate a UTF exception
|
||||
addLogEntry("Cannot read refreshToken from: " ~ appConfig.refreshTokenFilePath);
|
||||
addLogEntry(" Error Reason:" ~ exception.msg);
|
||||
authorised = false;
|
||||
}
|
||||
// We are already authenticated
|
||||
authorised = true;
|
||||
}
|
||||
|
||||
if (refreshToken.empty) {
|
||||
// PROBLEM ... CODING TO DO ??????????
|
||||
if (debugLogging) {addLogEntry("DEBUG: refreshToken is empty !!!!!!!!!!", ["debug"]);}
|
||||
} else {
|
||||
// Authenticate via standard method
|
||||
if (!exists(appConfig.refreshTokenFilePath)) {
|
||||
if (debugLogging) {addLogEntry("Application has no 'refresh_token' thus needs to be authenticated", ["debug"]);}
|
||||
authorised = authorise();
|
||||
} else {
|
||||
// Try and read the value from the appConfig if it is set, rather than trying to read the value from disk
|
||||
if (!appConfig.refreshToken.empty) {
|
||||
if (debugLogging) {addLogEntry("Read token from appConfig", ["debug"]);}
|
||||
refreshToken = strip(appConfig.refreshToken);
|
||||
authorised = true;
|
||||
} else {
|
||||
// Try and read the file from disk
|
||||
try {
|
||||
refreshToken = strip(readText(appConfig.refreshTokenFilePath));
|
||||
// is the refresh_token empty?
|
||||
if (refreshToken.empty) {
|
||||
addLogEntry("RefreshToken exists but is empty: " ~ appConfig.refreshTokenFilePath);
|
||||
authorised = authorise();
|
||||
} else {
|
||||
// Existing token not empty
|
||||
authorised = true;
|
||||
// update appConfig.refreshToken
|
||||
appConfig.refreshToken = refreshToken;
|
||||
}
|
||||
} catch (FileException exception) {
|
||||
authorised = authorise();
|
||||
} catch (std.utf.UTFException exception) {
|
||||
// path contains characters which generate a UTF exception
|
||||
addLogEntry("Cannot read refreshToken from: " ~ appConfig.refreshTokenFilePath);
|
||||
addLogEntry(" Error Reason:" ~ exception.msg);
|
||||
authorised = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshToken.empty) {
|
||||
// PROBLEM ... CODING TO DO ??????????
|
||||
if (debugLogging) {addLogEntry("DEBUG: refreshToken is empty !!!!!!!!!!", ["debug"]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -398,94 +412,221 @@ class OneDriveApi {
|
|||
|
||||
// Authenticate this client against Microsoft OneDrive API
|
||||
bool authorise() {
|
||||
|
||||
char[] response;
|
||||
// What URL should be presented to the user to access
|
||||
string url = authUrl ~ "?client_id=" ~ clientId ~ authScope ~ redirectUrl;
|
||||
// Configure automated authentication if --auth-files authUrl:responseUrl is being used
|
||||
string authFilesString = appConfig.getValueString("auth_files");
|
||||
string authResponseString = appConfig.getValueString("auth_response");
|
||||
|
||||
if (!authResponseString.empty) {
|
||||
// read the response from authResponseString
|
||||
response = cast(char[]) authResponseString;
|
||||
} else if (authFilesString != "") {
|
||||
string[] authFiles = authFilesString.split(":");
|
||||
string authUrl = authFiles[0];
|
||||
string responseUrl = authFiles[1];
|
||||
|
||||
try {
|
||||
auto authUrlFile = File(authUrl, "w");
|
||||
authUrlFile.write(url);
|
||||
authUrlFile.close();
|
||||
} catch (FileException exception) {
|
||||
// There was a file system error
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
// Must force exit here, allow logging to be done
|
||||
forceExit();
|
||||
} catch (ErrnoException exception) {
|
||||
// There was a file system error
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
// Must force exit here, allow logging to be done
|
||||
forceExit();
|
||||
}
|
||||
|
||||
addLogEntry("Client requires authentication before proceeding. Waiting for --auth-files elements to be available.");
|
||||
|
||||
while (!exists(responseUrl)) {
|
||||
Thread.sleep(dur!("msecs")(100));
|
||||
}
|
||||
|
||||
// read response from provided from OneDrive
|
||||
try {
|
||||
response = cast(char[]) read(responseUrl);
|
||||
} catch (OneDriveException exception) {
|
||||
// exception generated
|
||||
displayOneDriveErrorMessage(exception.msg, getFunctionName!({}));
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to remove old files
|
||||
try {
|
||||
std.file.remove(authUrl);
|
||||
std.file.remove(responseUrl);
|
||||
} catch (FileException exception) {
|
||||
addLogEntry("Cannot remove files " ~ authUrl ~ " " ~ responseUrl);
|
||||
return false;
|
||||
// Has the client been configured to use Intune SSO via Microsoft Identity Broker (microsoft-identity-broker) dbus session
|
||||
if (appConfig.getValueBool("use_intune_sso")) {
|
||||
// The client is configured to use Intune SSO via Microsoft Identity Broker dbus session
|
||||
// Do we have a saved account file?
|
||||
if (!exists(appConfig.intuneAccountDetailsFilePath)) {
|
||||
// No file exists locally
|
||||
auto intuneAuthResult = acquire_token_interactive(appConfig.getValueString("application_id"));
|
||||
JSONValue intuneBrokerJSONData = intuneAuthResult.brokerTokenResponse;
|
||||
|
||||
// Is the response JSON data valid?
|
||||
if ((intuneBrokerJSONData.type() == JSONType.object)) {
|
||||
// Does the JSON data have the required authentication elements:
|
||||
// - accessToken
|
||||
// - account
|
||||
// - expiresOn
|
||||
if ((hasAccessTokenData(intuneBrokerJSONData)) && (hasAccountData(intuneBrokerJSONData)) && (hasExpiresOn(intuneBrokerJSONData))) {
|
||||
// Details exist
|
||||
processIntuneResponse(intuneBrokerJSONData);
|
||||
// Return that we are authenticated
|
||||
return true;
|
||||
} else {
|
||||
// no ... expected values not available
|
||||
addLogEntry("Required JSON elements are not present in the Intune JSON response");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Not a valid JSON response
|
||||
addLogEntry("Invalid JSON Intune JSON response when attempting access authentication");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// The account information is available in a saved file. Read this file in and attempt a silent authentication
|
||||
try {
|
||||
appConfig.intuneAccountDetails = strip(readText(appConfig.intuneAccountDetailsFilePath));
|
||||
|
||||
// Is the 'intune_account' empty?
|
||||
if (appConfig.intuneAccountDetails.empty) {
|
||||
addLogEntry("The 'intune_account' file exists but is empty: " ~ appConfig.intuneAccountDetailsFilePath);
|
||||
// No .. remove the file and perform the interactive authentication
|
||||
safeRemove(appConfig.intuneAccountDetailsFilePath);
|
||||
// Attempt interactive authentication
|
||||
authorise();
|
||||
return true;
|
||||
} else {
|
||||
// We have loaded some Intune Account details, try and use them
|
||||
auto intuneAuthResult = acquire_token_silently(appConfig.intuneAccountDetails, appConfig.getValueString("application_id"));
|
||||
JSONValue intuneBrokerJSONData = intuneAuthResult.brokerTokenResponse;
|
||||
// Is the JSON data valid?
|
||||
if ((intuneBrokerJSONData.type() == JSONType.object)) {
|
||||
// Does the JSON data have the required authentication elements:
|
||||
// - accessToken
|
||||
// - account
|
||||
// - expiresOn
|
||||
if ((hasAccessTokenData(intuneBrokerJSONData)) && (hasAccountData(intuneBrokerJSONData)) && (hasExpiresOn(intuneBrokerJSONData))) {
|
||||
// Details exist
|
||||
processIntuneResponse(intuneBrokerJSONData);
|
||||
// Return that we are authenticated
|
||||
return true;
|
||||
} else {
|
||||
// no ... expected values not available
|
||||
addLogEntry("Required JSON elements are not present in the Intune JSON response");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// No .. remove the file and perform the interactive authentication
|
||||
safeRemove(appConfig.intuneAccountDetailsFilePath);
|
||||
// Attempt interactive authentication
|
||||
authorise();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (FileException exception) {
|
||||
return false;
|
||||
} catch (std.utf.UTFException exception) {
|
||||
// path contains characters which generate a UTF exception
|
||||
addLogEntry("Cannot read 'intune_account' file on disk from: " ~ appConfig.intuneAccountDetailsFilePath);
|
||||
addLogEntry(" Error Reason:" ~ exception.msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Are we in a --dry-run scenario?
|
||||
if (!appConfig.getValueBool("dry_run")) {
|
||||
// No --dry-run is being used
|
||||
addLogEntry("Authorise this application by visiting:\n", ["consoleOnly"]);
|
||||
addLogEntry(url ~ "\n", ["consoleOnly"]);
|
||||
addLogEntry("Enter the response uri from your browser: ", ["consoleOnlyNoNewLine"]);
|
||||
readln(response);
|
||||
appConfig.applicationAuthorizeResponseUri = true;
|
||||
// Normal authentication method
|
||||
char[] response;
|
||||
// What URL should be presented to the user to access
|
||||
string url = authUrl ~ "?client_id=" ~ clientId ~ authScope ~ redirectUrl;
|
||||
// Configure automated authentication if --auth-files authUrl:responseUrl is being used
|
||||
string authFilesString = appConfig.getValueString("auth_files");
|
||||
string authResponseString = appConfig.getValueString("auth_response");
|
||||
|
||||
if (!authResponseString.empty) {
|
||||
// read the response from authResponseString
|
||||
response = cast(char[]) authResponseString;
|
||||
} else if (authFilesString != "") {
|
||||
string[] authFiles = authFilesString.split(":");
|
||||
string authUrl = authFiles[0];
|
||||
string responseUrl = authFiles[1];
|
||||
|
||||
try {
|
||||
auto authUrlFile = File(authUrl, "w");
|
||||
authUrlFile.write(url);
|
||||
authUrlFile.close();
|
||||
} catch (FileException exception) {
|
||||
// There was a file system error
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
// Must force exit here, allow logging to be done
|
||||
forceExit();
|
||||
} catch (ErrnoException exception) {
|
||||
// There was a file system error
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
// Must force exit here, allow logging to be done
|
||||
forceExit();
|
||||
}
|
||||
|
||||
addLogEntry("Client requires authentication before proceeding. Waiting for --auth-files elements to be available.");
|
||||
|
||||
while (!exists(responseUrl)) {
|
||||
Thread.sleep(dur!("msecs")(100));
|
||||
}
|
||||
|
||||
// read response from provided from OneDrive
|
||||
try {
|
||||
response = cast(char[]) read(responseUrl);
|
||||
} catch (OneDriveException exception) {
|
||||
// exception generated
|
||||
displayOneDriveErrorMessage(exception.msg, getFunctionName!({}));
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to remove old files
|
||||
try {
|
||||
std.file.remove(authUrl);
|
||||
std.file.remove(responseUrl);
|
||||
} catch (FileException exception) {
|
||||
addLogEntry("Cannot remove files " ~ authUrl ~ " " ~ responseUrl);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// The application cannot be authorised when using --dry-run as we have to write out the authentication data, which negates the whole 'dry-run' process
|
||||
addLogEntry();
|
||||
addLogEntry("The application requires authorisation, which involves saving authentication data on your system. Application authorisation cannot be completed when using the '--dry-run' option.");
|
||||
addLogEntry();
|
||||
addLogEntry("To authorise the application please use your original command without '--dry-run'.");
|
||||
addLogEntry();
|
||||
addLogEntry("To exclusively authorise the application without performing any additional actions, do not add '--sync' or '--monitor' to your command line.");
|
||||
addLogEntry();
|
||||
forceExit();
|
||||
// Are we in a --dry-run scenario?
|
||||
if (!appConfig.getValueBool("dry_run")) {
|
||||
// No --dry-run is being used
|
||||
addLogEntry("Authorise this application by visiting:\n", ["consoleOnly"]);
|
||||
addLogEntry(url ~ "\n", ["consoleOnly"]);
|
||||
addLogEntry("Enter the response uri from your browser: ", ["consoleOnlyNoNewLine"]);
|
||||
readln(response);
|
||||
appConfig.applicationAuthorizeResponseUri = true;
|
||||
} else {
|
||||
// The application cannot be authorised when using --dry-run as we have to write out the authentication data, which negates the whole 'dry-run' process
|
||||
addLogEntry();
|
||||
addLogEntry("The application requires authorisation, which involves saving authentication data on your system. Application authorisation cannot be completed when using the '--dry-run' option.");
|
||||
addLogEntry();
|
||||
addLogEntry("To authorise the application please use your original command without '--dry-run'.");
|
||||
addLogEntry();
|
||||
addLogEntry("To exclusively authorise the application without performing any additional actions, do not add '--sync' or '--monitor' to your command line.");
|
||||
addLogEntry();
|
||||
forceExit();
|
||||
}
|
||||
}
|
||||
|
||||
// match the authorisation code
|
||||
auto c = matchFirst(response, r"(?:[\?&]code=)([\w\d-.]+)");
|
||||
if (c.empty) {
|
||||
addLogEntry("An empty or invalid response uri was entered");
|
||||
return false;
|
||||
}
|
||||
c.popFront(); // skip the whole match
|
||||
redeemToken(c.front);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process Intune JSON response data
|
||||
void processIntuneResponse(JSONValue intuneBrokerJSONData) {
|
||||
// Use the provided JSON data and configure elements, save JSON data to disk for reuse
|
||||
|
||||
long expiresOnMs = intuneBrokerJSONData["expiresOn"].integer();
|
||||
// Convert to SysTime
|
||||
SysTime expiryTime = SysTime.fromUnixTime(expiresOnMs / 1000);
|
||||
|
||||
// Store in appConfig (to match standard flow)
|
||||
appConfig.accessTokenExpiration = expiryTime;
|
||||
addLogEntry("Intune access token expires at: " ~ to!string(appConfig.accessTokenExpiration));
|
||||
|
||||
// Configure the 'accessToken' based on Intune response
|
||||
appConfig.accessToken = "bearer " ~ strip(intuneBrokerJSONData["accessToken"].str);
|
||||
|
||||
// Do we print the current access token
|
||||
debugOutputAccessToken();
|
||||
|
||||
// In order to support silent renewal of the access token, when the access token expires, we must store the Intune account data
|
||||
appConfig.intuneAccountDetails = to!string(intuneBrokerJSONData["account"]);
|
||||
|
||||
// try and update the 'intune_account' file on disk for reuse later
|
||||
try {
|
||||
if (debugLogging) {addLogEntry("Updating 'intune_account' on disk", ["debug"]);}
|
||||
std.file.write(appConfig.intuneAccountDetailsFilePath, appConfig.intuneAccountDetails);
|
||||
if (debugLogging) {addLogEntry("Setting file permissions for: " ~ appConfig.intuneAccountDetailsFilePath, ["debug"]);}
|
||||
appConfig.intuneAccountDetailsFilePath.setAttributes(appConfig.returnSecureFilePermission());
|
||||
} catch (FileException exception) {
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
}
|
||||
}
|
||||
|
||||
// Do we print the current access token
|
||||
void debugOutputAccessToken() {
|
||||
if (appConfig.verbosityCount > 1) {
|
||||
if (appConfig.getValueBool("debug_https")) {
|
||||
if (appConfig.getValueBool("print_token")) {
|
||||
// This needs to be highly restricted in output ....
|
||||
if (debugLogging) {addLogEntry("CAUTION - KEEP THIS SAFE: Current access token: " ~ to!string(appConfig.accessToken), ["debug"]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// match the authorization code
|
||||
auto c = matchFirst(response, r"(?:[\?&]code=)([\w\d-.]+)");
|
||||
if (c.empty) {
|
||||
addLogEntry("An empty or invalid response uri was entered");
|
||||
return false;
|
||||
}
|
||||
c.popFront(); // skip the whole match
|
||||
redeemToken(c.front);
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/drive_get
|
||||
|
|
@ -870,6 +1011,8 @@ class OneDriveApi {
|
|||
|
||||
private void acquireToken(char[] postData) {
|
||||
JSONValue response;
|
||||
// Debug this input
|
||||
if (debugLogging) {addLogEntry("Manual Auth postData = " ~ to!string(postData), ["debug"]);}
|
||||
|
||||
try {
|
||||
response = post(tokenUrl, postData, null, true, "application/x-www-form-urlencoded");
|
||||
|
|
@ -893,6 +1036,9 @@ class OneDriveApi {
|
|||
}
|
||||
|
||||
if (response.type() == JSONType.object) {
|
||||
// Debug this response
|
||||
if (debugLogging) {addLogEntry("Manual Auth Response JSON = " ~ to!string(response), ["debug"]);}
|
||||
|
||||
// Has the client been configured to use read_only_auth_scope
|
||||
if (appConfig.getValueBool("read_only_auth_scope")) {
|
||||
// read_only_auth_scope has been configured
|
||||
|
|
@ -922,17 +1068,15 @@ class OneDriveApi {
|
|||
appConfig.accessToken = "bearer " ~ strip(response["access_token"].str);
|
||||
|
||||
// Do we print the current access token
|
||||
if (appConfig.verbosityCount > 1) {
|
||||
if (appConfig.getValueBool("debug_https")) {
|
||||
if (appConfig.getValueBool("print_token")) {
|
||||
// This needs to be highly restricted in output ....
|
||||
if (debugLogging) {addLogEntry("CAUTION - KEEP THIS SAFE: Current access token: " ~ to!string(appConfig.accessToken), ["debug"]);}
|
||||
}
|
||||
}
|
||||
}
|
||||
debugOutputAccessToken();
|
||||
|
||||
// Obtain the 'refresh_token' and its expiry
|
||||
refreshToken = strip(response["refresh_token"].str);
|
||||
appConfig.accessTokenExpiration = Clock.currTime() + dur!"seconds"(response["expires_in"].integer());
|
||||
|
||||
// Debug this response
|
||||
if (debugLogging) {addLogEntry("appConfig.accessTokenExpiration = " ~ to!string(appConfig.accessTokenExpiration), ["debug"]);}
|
||||
|
||||
if (!dryRun) {
|
||||
// Update the refreshToken in appConfig so that we can reuse it
|
||||
if (appConfig.refreshToken.empty) {
|
||||
|
|
@ -948,12 +1092,12 @@ class OneDriveApi {
|
|||
}
|
||||
}
|
||||
|
||||
// try and update the refresh_token file on disk
|
||||
// try and update the 'refresh_token' file on disk
|
||||
try {
|
||||
if (debugLogging) {addLogEntry("Updating refreshToken on disk", ["debug"]);}
|
||||
if (debugLogging) {addLogEntry("Updating 'refresh_token' on disk", ["debug"]);}
|
||||
std.file.write(appConfig.refreshTokenFilePath, refreshToken);
|
||||
if (debugLogging) {addLogEntry("Setting file permissions for: " ~ appConfig.refreshTokenFilePath, ["debug"]);}
|
||||
appConfig.refreshTokenFilePath.setAttributes(appConfig.returnRequiredFilePermissions());
|
||||
appConfig.refreshTokenFilePath.setAttributes(appConfig.returnSecureFilePermission());
|
||||
} catch (FileException exception) {
|
||||
// display the error message
|
||||
displayFileSystemErrorMessage(exception.msg, getFunctionName!({}));
|
||||
|
|
@ -976,20 +1120,46 @@ class OneDriveApi {
|
|||
}
|
||||
}
|
||||
|
||||
private void newToken() {
|
||||
private void generateNewAccessToken() {
|
||||
if (debugLogging) {addLogEntry("Need to generate a new access token for Microsoft OneDrive", ["debug"]);}
|
||||
auto postData = appender!(string)();
|
||||
postData ~= "client_id=" ~ clientId;
|
||||
postData ~= "&redirect_uri=" ~ redirectUrl;
|
||||
postData ~= "&refresh_token=" ~ to!string(refreshToken);
|
||||
postData ~= "&grant_type=refresh_token";
|
||||
acquireToken(postData.data.dup);
|
||||
// Has the client been configured to use Intune SSO via Microsoft Identity Broker (microsoft-identity-broker) dbus session
|
||||
if (appConfig.getValueBool("use_intune_sso")) {
|
||||
// The client is configured to use Intune SSO via Microsoft Identity Broker dbus session
|
||||
auto intuneAuthResult = acquire_token_silently(appConfig.intuneAccountDetails, appConfig.getValueString("application_id"));
|
||||
JSONValue intuneBrokerJSONData = intuneAuthResult.brokerTokenResponse;
|
||||
// Is the JSON data valid?
|
||||
if ((intuneBrokerJSONData.type() == JSONType.object)) {
|
||||
// Does the JSON data have the required renewal elements:
|
||||
// - accessToken
|
||||
// - account
|
||||
// - expiresOn
|
||||
if ((hasAccessTokenData(intuneBrokerJSONData)) && (hasAccountData(intuneBrokerJSONData)) && (hasExpiresOn(intuneBrokerJSONData))) {
|
||||
// Details exist
|
||||
processIntuneResponse(intuneBrokerJSONData);
|
||||
} else {
|
||||
// no ... expected values not available
|
||||
addLogEntry("Required Intune JSON elements are not present in the Intune JSON response");
|
||||
}
|
||||
} else {
|
||||
// Not a valid JSON response
|
||||
addLogEntry("Invalid Intune JSON response when attempting access token renewal");
|
||||
}
|
||||
} else {
|
||||
// Normal authentication method
|
||||
auto postData = appender!(string)();
|
||||
postData ~= "client_id=" ~ clientId;
|
||||
postData ~= "&redirect_uri=" ~ redirectUrl;
|
||||
postData ~= "&refresh_token=" ~ to!string(refreshToken);
|
||||
postData ~= "&grant_type=refresh_token";
|
||||
acquireToken(postData.data.dup);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the existing access token has expired, if it has, generate a new one
|
||||
private void checkAccessTokenExpired() {
|
||||
if (Clock.currTime() >= appConfig.accessTokenExpiration) {
|
||||
if (debugLogging) {addLogEntry("Microsoft OneDrive Access Token has expired. Must generate a new Microsoft OneDrive Access Token", ["debug"]);}
|
||||
newToken();
|
||||
generateNewAccessToken();
|
||||
} else {
|
||||
if (debugLogging) {addLogEntry("Existing Microsoft OneDrive Access Token Expires: " ~ to!string(appConfig.accessTokenExpiration), ["debug"]);}
|
||||
}
|
||||
|
|
|
|||
15
src/util.d
15
src/util.d
|
|
@ -1352,6 +1352,21 @@ bool hasLastModifiedByUserDisplayName(const ref JSONValue item) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check Intune JSON response for 'accessToken'
|
||||
bool hasAccessTokenData(const ref JSONValue item) {
|
||||
return ("accessToken" in item) != null;
|
||||
}
|
||||
|
||||
// Check Intune JSON response for 'account'
|
||||
bool hasAccountData(const ref JSONValue item) {
|
||||
return ("account" in item) != null;
|
||||
}
|
||||
|
||||
// Check Intune JSON response for 'expiresOn'
|
||||
bool hasExpiresOn(const ref JSONValue item) {
|
||||
return ("expiresOn" in item) != null;
|
||||
}
|
||||
|
||||
// Convert bytes to GB
|
||||
string byteToGibiByte(ulong bytes) {
|
||||
if (bytes == 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue