Skip to content

OAuth google calendar auth using golang external helper #341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.mo
/dist
/package/metadata.json
package/contents/scripts/local-http
78 changes: 78 additions & 0 deletions google-oauth-helper/local-http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"time"
)

type oAuthHandler struct{}

var (
clientID string
clientSecret string
)

func (oAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.ParseForm()

if r.FormValue("code") == "" {
w.Write([]byte("code required"))
return
}

data := url.Values{
"client_id": {clientID},
"client_secret": {clientSecret},
"code": {r.FormValue("code")},
"grant_type": {"authorization_code"},
"redirect_uri": {"http://127.0.0.1:8080"},
}

rt, err := http.PostForm("https://oauth2.googleapis.com/token", data)
if err != nil {
fmt.Println("error on processing token request: ", err)
os.Exit(3)
return
}

defer rt.Body.Close()
rtjdata, err := io.ReadAll(rt.Body)
if err != nil {
fmt.Println("error on reading result of token request: ", err)
os.Exit(4)
return
}

w.Write([]byte("please close this window"))

fmt.Println(string(rtjdata))
go func() {
time.Sleep(time.Second)
os.Exit(0)
}()

}

func main() {

if len(os.Args) != 3 {
fmt.Println("invalid params")
os.Exit(2)
}

clientID, clientSecret = os.Args[1], os.Args[2]

openURL("https://accounts.google.com/o/oauth2/v2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Ftasks&response_type=code&redirect_uri=http%3A%2F%2F127.0.0.1:8080&client_id=" + clientID)

http.ListenAndServe(":8080", oAuthHandler{})
}

func openURL(url string) error {
// this works only for linux... but plasma is also for linux? :)
return exec.Command("xdg-open", url).Start()
}
3 changes: 3 additions & 0 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# This script detects if the widget is already installed.
# If it is, it will use --upgrade instead and restart plasmashell.

### compile golang helper
go build -o package/contents/scripts/local-http google-oauth-helper/local-http.go || { echo "unable to compile go helper"; exit; }

packageNamespace=`kreadconfig5 --file="$PWD/package/metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
restartPlasmashell=false

Expand Down
6 changes: 6 additions & 0 deletions package/contents/config/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@
<entry name="agendaInProgressColor" type="string" stringType="color">
<default></default>
</entry>
<entry name="agendaPastColor" type="string" stringType="color">
<default></default>
</entry>
<entry name="agendaFontSize" type="uint">
<default>0</default>
</entry>
Expand Down Expand Up @@ -299,6 +302,9 @@
<entry name="agenda_inProgressColor" type="string" stringType="color">
<default></default>
</entry>
<entry name="agenda_pastColor" type="string" stringType="color">
<default></default>
</entry>
<entry name="agenda_fontSize" type="uint">
<default>0</default>
</entry>
Expand Down
10 changes: 8 additions & 2 deletions package/contents/ui/AgendaEventItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ LinkRect {
implicitHeight: contents.implicitHeight

property bool eventItemInProgress: false
property bool eventItemPast: false
function checkIfInProgress() {
if (model.startDateTime && timeModel.currentTime && model.endDateTime) {
eventItemInProgress = model.startDateTime <= timeModel.currentTime && timeModel.currentTime <= model.endDateTime
} else {
eventItemInProgress = false
}
// console.log('checkIfInProgress()', model.start, timeModel.currentTime, model.end)
if (model.startDateTime && timeModel.currentTime && model.endDateTime) {
eventItemPast = model.endDateTime < timeModel.currentTime
} else {
eventItemPast = false
}
}
Connections {
target: timeModel
Expand Down Expand Up @@ -124,7 +130,7 @@ LinkRect {
return model.summary
}
}
color: eventItemInProgress ? inProgressColor : PlasmaCore.ColorScope.textColor
color: eventItemInProgress ? inProgressColor : ( eventItemPast ? pastColor : PlasmaCore.ColorScope.textColor )
font.pointSize: -1
font.pixelSize: appletConfig.agendaFontSize
font.weight: eventItemInProgress ? inProgressFontWeight : Font.Normal
Expand All @@ -146,7 +152,7 @@ LinkRect {
return eventTimestamp
}
}
color: eventItemInProgress ? inProgressColor : PlasmaCore.ColorScope.textColor
color: eventItemInProgress ? inProgressColor : ( eventItemPast ? pastColor : PlasmaCore.ColorScope.textColor )
opacity: eventItemInProgress ? 1 : 0.75
font.pointSize: -1
font.pixelSize: appletConfig.agendaFontSize
Expand Down
3 changes: 3 additions & 0 deletions package/contents/ui/AgendaView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Item {
property color inProgressColor: appletConfig.agendaInProgressColor
property int inProgressFontWeight: Font.Bold

property color pastColor: appletConfig.agendaPastColor
property int pastFontWeight: Font.Light

property color isOverdueColor: PlasmaCore.ColorScope.negativeTextColor
property int isOverdueFontWeight: Font.Bold

Expand Down
2 changes: 2 additions & 0 deletions package/contents/ui/AppletConfig.qml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ QtObject {
property color agendaHoverBackground: alternateBackgroundColor
property color agendaInProgressColorDefault: theme.highlightColor
property color agendaInProgressColor: plasmoid.configuration.agendaInProgressColor || agendaInProgressColorDefault
property color agendaPastColorDefault: theme.highlightColor
property color agendaPastColor: plasmoid.configuration.agendaPastColor || agendaPastColorDefault

property int agendaColumnSpacing: 10 * units.devicePixelRatio
property int agendaDaySpacing: plasmoid.configuration.agendaDaySpacing * units.devicePixelRatio
Expand Down
5 changes: 5 additions & 0 deletions package/contents/ui/config/ConfigAgenda.qml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ ConfigPage {
label: i18n("In Progress")
defaultColor: config.agendaInProgressColorDefault
}
ConfigColor {
configKey: 'agendaPastColor'
label: i18n("Event in past")
defaultColor: config.agendaPastColorDefault
}
}

ConfigSection {
Expand Down
61 changes: 2 additions & 59 deletions package/contents/ui/config/ConfigGoogleCalendar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -118,68 +118,11 @@ ConfigPage {
color: readableNegativeTextColor
wrapMode: Text.Wrap
}
LinkText {
Layout.fillWidth: true
text: i18n("Visit <a href=\"%1\">%2</a> (opens in your web browser). After you login and give permission to access your calendar, it will give you a code to paste below.", googleLoginManager.authorizationCodeUrl, 'https://accounts.google.com/...')
color: readableNegativeTextColor
wrapMode: Text.Wrap

// Tooltip
// QQC2.ToolTip.visible: !!hoveredLink
// QQC2.ToolTip.text: googleLoginManager.authorizationCodeUrl

// ContextMenu
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
onPressAndHold: {
if (mouse.source === Qt.MouseEventNotSynthesized) {
contextMenu.popup()
}
}

QQC2.Menu {
id: contextMenu
QQC2.MenuItem {
text: i18n("Copy Link")
onTriggered: clipboardHelper.copyText(googleLoginManager.authorizationCodeUrl)
}
}

TextEdit {
id: clipboardHelper
visible: false
function copyText(text) {
clipboardHelper.text = text
clipboardHelper.selectAll()
clipboardHelper.copy()
}
}
}
}
RowLayout {
TextField {
id: authorizationCodeInput
Layout.fillWidth: true

placeholderText: i18n("Enter code here (Eg: %1)", '1/2B3C4defghijklmnopqrst-uvwxyz123456789ab-cdeFGHIJKlmnio')
text: ""
}
Button {
text: i18n("Submit")
text: i18n("Authorize")
onClicked: {
if (authorizationCodeInput.text) {
googleLoginManager.fetchAccessToken({
authorizationCode: authorizationCodeInput.text,
})
} else {
messageWidget.err(i18n("Invalid Google Authorization Code"))
}
googleLoginManager.fetchAccessToken()
}
}
}
Expand Down
48 changes: 15 additions & 33 deletions package/contents/ui/config/GoogleLoginManager.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "../lib/Requests.js" as Requests

Item {
id: session
ExecUtil { id: executable }

Logger {
id: logger
Expand Down Expand Up @@ -73,48 +74,29 @@ Item {
signal error(string err)


//---
readonly property string authorizationCodeUrl: {
var url = 'https://accounts.google.com/o/oauth2/v2/auth'
url += '?scope=' + encodeURIComponent('https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/tasks')
url += '&response_type=code'
url += '&redirect_uri=' + encodeURIComponent('urn:ietf:wg:oauth:2.0:oob')
url += '&client_id=' + encodeURIComponent(plasmoid.configuration.latestClientId)
return url
}
function fetchAccessToken() {

function fetchAccessToken(args) {
var url = 'https://www.googleapis.com/oauth2/v4/token'
Requests.post({
url: url,
data: {
client_id: plasmoid.configuration.latestClientId,
client_secret: plasmoid.configuration.latestClientSecret,
code: args.authorizationCode,
grant_type: 'authorization_code',
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
},
}, function(err, data, xhr) {
logger.debug('/oauth2/v4/token Response', data)
logger.debug('gcal.fetchAccessToken')
var cmd = [plasmoid.file("", "scripts/local-http"), encodeURIComponent(plasmoid.configuration.latestClientId), encodeURIComponent(plasmoid.configuration.latestClientSecret)]
logger.debug('gcal.fetchAccessToken', cmd)

// Check for errors
if (err) {
handleError(err, null)
executable.exec(cmd, function(cmd, exitCode, exitStatus, stdout, stderr) {
if (exitCode) {
logger.log('gcal.fetchAccessToken.stderr', stderr)
logger.log('gcal.fetchAccessToken.stdout', stdout)
return
}

try {
data = JSON.parse(data)
logger.log('gcal.fetchAccessToken.stdout', stdout)
var data = JSON.parse(stdout)
updateAccessToken(data)
} catch (e) {
handleError('Error parsing /oauth2/v4/token data as JSON', null)
return
}
if (data && data.error) {
handleError(err, data)
logger.log('gcal.fetchAccessToken.e', e)
handleError('Error parsing oauth2/token data as JSON', null)
return
}

// Ready
updateAccessToken(data)
})
}

Expand Down