├── .chglog
├── CHANGELOG.tpl.md
└── config.yml
├── .dockerignore
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .golangci.yaml
├── .license-ranger.json
├── CHANGELOG.md
├── Caddyfile.dev
├── Caddyfile.example
├── Dockerfile
├── Dockerfile.build
├── Dockerfile.release
├── Jenkinsfile
├── LICENSE.txt
├── Makefile
├── NOTICE.txt
├── README.md
├── bootstrap
├── bootstrap.go
├── cookie.go
├── dummy.go
├── guest.go
├── kc.go
├── ldap.go
├── managers.go
└── utils.go
├── claims.go
├── cmd
├── konnectd
│ ├── env.go
│ ├── healthcheck.go
│ ├── jwk.go
│ ├── log.go
│ ├── main.go
│ ├── serve.go
│ └── utils.go
├── root.go
└── version.go
├── config
└── config.go
├── context.go
├── doc.go
├── docs
└── konnect-identifier-api-v1.yaml
├── encryption
├── encryption.go
├── encryption_test.go
└── generate.go
├── examples
└── coookieserver.py
├── go.mod
├── go.sum
├── identifier-registration.yaml.in
├── identifier
├── .env
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── Makefile
├── README.md
├── README.react.md
├── api.go
├── backends
│ ├── backend.go
│ ├── kc.go
│ ├── kc
│ │ └── consts.go
│ ├── ldap.go
│ └── ldap
│ │ └── const.go
├── claims.go
├── config.go
├── cookie.go
├── flows.go
├── handlers.go
├── i18n
│ ├── Makefile
│ ├── de.po
│ ├── es.po
│ ├── fr.po
│ ├── hi.po
│ ├── hr.po
│ ├── hu.po
│ ├── is.po
│ ├── it.po
│ ├── ja.po
│ ├── konnect-identifier.pot
│ ├── nb.po
│ ├── nl.po
│ ├── pl.po
│ ├── pt_PT.po
│ ├── ru.po
│ └── sl.po
├── identifier.go
├── meta
│ ├── meta.go
│ └── scopes
│ │ ├── definition.go
│ │ └── scopes.go
├── models.go
├── modes.go
├── oauth2.go
├── package.json
├── public
│ ├── index.html
│ └── static
│ │ └── favicon.ico
├── saml2.go
├── src
│ ├── Main.js
│ ├── Main.test.js
│ ├── Makefile
│ ├── Routes.js
│ ├── actions
│ │ ├── common.js
│ │ ├── login.js
│ │ ├── types.js
│ │ └── utils.js
│ ├── app.css
│ ├── app.js
│ ├── components
│ │ ├── ClientDisplayName.js
│ │ ├── Loading.js
│ │ ├── PrivateRoute.js
│ │ ├── RedirectWithQuery.js
│ │ ├── ResponsiveDialog.js
│ │ ├── ResponsiveScreen.js
│ │ └── ScopesList.js
│ ├── containers
│ │ ├── Goodbye
│ │ │ ├── Goodbyescreen.js
│ │ │ └── index.js
│ │ ├── Login
│ │ │ ├── Chooseaccount.js
│ │ │ ├── Consent.js
│ │ │ ├── Login.js
│ │ │ ├── Loginscreen.js
│ │ │ └── index.js
│ │ └── Welcome
│ │ │ ├── Welcomescreen.js
│ │ │ └── index.js
│ ├── errors
│ │ └── index.js
│ ├── fancy-background.css
│ ├── images
│ │ ├── .gitignore
│ │ ├── Makefile
│ │ ├── kopano-augmenting-teamwork-bg-7680.jpg
│ │ ├── kopano-augmenting-teamwork-overlay.svg
│ │ ├── kopano-konnect-icon.svg
│ │ ├── kopano-logo.svg
│ │ ├── loginscreen-bg-thumb.svg.in
│ │ └── loginscreen-bg.css.in
│ ├── index.js
│ ├── locales
│ │ └── index.js
│ ├── models
│ │ └── hello.js
│ ├── reducers
│ │ ├── common.js
│ │ ├── index.js
│ │ └── login.js
│ ├── store.js
│ ├── utils.js
│ └── version.js
├── trampolin.go
├── user.go
├── utils.go
└── yarn.lock
├── identity
├── auth.go
├── authorities
│ ├── authorities.go
│ ├── models.go
│ ├── oidc.go
│ ├── registry.go
│ ├── saml2.go
│ └── samlext
│ │ ├── dsig.go
│ │ ├── idplogoutrequest.go
│ │ ├── idplogoutresponse.go
│ │ ├── logoutrequest.go
│ │ └── logoutresponse.go
├── authrecord.go
├── clients
│ ├── claims.go
│ ├── clients.go
│ ├── models.go
│ ├── registry.go
│ └── registry_test.go
├── config.go
├── context.go
├── errors.go
├── manager.go
├── managers
│ ├── cookie.go
│ ├── dummy.go
│ ├── encryption.go
│ ├── guest.go
│ ├── identifier.go
│ └── utils.go
├── user.go
└── utils.go
├── managers
└── managers.go
├── oidc
├── claims.go
├── code
│ ├── manager.go
│ └── managers
│ │ └── memorymap.go
├── errors.go
├── oidc.go
├── payload
│ ├── authentication.go
│ ├── claims.go
│ ├── endsession.go
│ ├── registration.go
│ ├── request.go
│ ├── schema.go
│ ├── session.go
│ ├── token.go
│ ├── userinfo.go
│ └── utils.go
└── provider
│ ├── config.go
│ ├── cookie.go
│ ├── handlers.go
│ ├── handlers_test.go
│ ├── html.go
│ ├── identity.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── session.go
│ ├── signing.go
│ ├── state.go
│ ├── subject.go
│ ├── tokens.go
│ └── utils.go
├── payloads.go
├── provider.go
├── scopes.go
├── scopes.yaml.in
├── scripts
├── README.md
├── build-konnectd-aci.sh
├── docker-entrypoint.sh
├── go-license-ranger.py
├── healthcheck.sh
├── js-license-ranger.js
├── konnectd.cfg
├── kopano-konnectd.binscript
└── kopano-konnectd.service
├── server
├── config.go
├── handlers.go
├── handlers_test.go
├── server.go
└── server_test.go
├── signing
├── jwk.go
└── jwt.go
├── utils
├── errorpage.go
├── errors.go
├── http.go
├── json.go
├── origin.go
├── redirect.go
├── schema.go
├── trusted.go
└── utils.go
└── version
└── version.go
/.chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | {{ if .Versions -}}
4 | ## Unreleased
5 | {{ if .Unreleased.CommitGroups -}}
6 | {{ range .Unreleased.CommitGroups -}}
7 | ### {{ .Title }}
8 | {{ range .Commits -}}
9 | {{ if not (hasSuffix (lower .Subject) " changelog") -}}
10 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
11 | {{ end -}}
12 | {{ end }}
13 | {{ end -}}
14 | {{ else }}
15 | {{ range .Unreleased.Commits -}}
16 | {{ if not (hasSuffix (lower .Subject) " changelog") -}}
17 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
18 | {{ end -}}
19 | {{ end }}
20 | {{ end -}}
21 | {{ end -}}
22 |
23 | {{ range .Versions }}
24 | ## {{ if .Tag.Previous }}{{ .Tag.Name }}{{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
25 | {{ if .CommitGroups -}}
26 | {{ range .CommitGroups -}}
27 | ### {{ .Title }}
28 | {{ range .Commits -}}
29 | {{ if not (hasSuffix (lower .Subject) " changelog") -}}
30 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
31 | {{ end -}}
32 | {{ end }}
33 | {{ end -}}
34 | {{ else }}
35 | {{ range .Commits -}}
36 | {{ if not (hasSuffix (lower .Subject) " changelog") -}}
37 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
38 | {{ end -}}
39 | {{ end }}
40 | {{ end -}}
41 |
42 | {{- if .NoteGroups -}}
43 | {{ range .NoteGroups -}}
44 | ### {{ .Title }}
45 | {{ range .Notes }}
46 | {{ .Body }}
47 | {{ end }}
48 | {{ end -}}
49 | {{ end -}}
50 | {{ end -}}
51 |
--------------------------------------------------------------------------------
/.chglog/config.yml:
--------------------------------------------------------------------------------
1 | style: bitbucket
2 | template: CHANGELOG.tpl.md
3 | info:
4 | title: CHANGELOG
5 | repository_url: https://stash.kopano.io/projects/KC/repos/konnect/
6 | options:
7 | commits:
8 | # filters:
9 | # Type:
10 | # - feat
11 | # - fix
12 | # - perf
13 | # - refactor
14 | commit_groups:
15 | # title_maps:
16 | # feat: Features
17 | # fix: Bug Fixes
18 | # perf: Performance Improvements
19 | # refactor: Code Refactoring
20 | header:
21 | pattern: "^(.*)$"
22 | pattern_maps:
23 | - Subject
24 | notes:
25 | keywords:
26 | - BREAKING CHANGE
27 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **
2 | !bin/konnectd
3 | !scripts/*.sh
4 | !identifier/build
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | charset = utf-8
9 | indent_style = tab
10 | indent_size = 4
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | ; Python: PEP8 defines 4 spaces for indentation
15 | [*.py]
16 | indent_style = space
17 | indent_size = 4
18 |
19 | ; YAML format, 2 spaces
20 | [*.{yaml,yml,yaml.in}]
21 | indent_style = space
22 | indent_size = 2
23 |
24 | ; HTML, CSS and JavaScript, 4 spaces
25 | [*.{html,css,js}]
26 | charset = utf-8
27 | indent_size = 2
28 | indent_style = space
29 | charset = utf-8
30 | end_of_line = lf
31 | insert_final_newline = true
32 | trim_trailing_whitespace = true
33 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "jsx": true
8 | }
9 | },
10 | "extends": "eslint:recommended",
11 | "env": {
12 | "browser": true,
13 | "es6": true
14 | },
15 | "rules": {
16 | "max-len": [
17 | "warn",
18 | 120
19 | ],
20 | "indent": [
21 | "error",
22 | 2,
23 | {
24 | "SwitchCase": 1
25 | }
26 | ],
27 | "camelcase": [
28 | "error",
29 | {
30 | "properties": "always"
31 | }
32 | ],
33 | "key-spacing": [
34 | "error",
35 | {
36 | "beforeColon": false,
37 | "afterColon": true
38 | }
39 | ],
40 | "comma-dangle": [
41 | "error",
42 | "never"
43 | ],
44 | "dot-notation": [
45 | "error"
46 | ],
47 | "semi": [
48 | "error",
49 | "always"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /bin
3 | /.gopath
4 | /.vscode
5 | /cmd/konnectd/debug
6 | /test/tests.*
7 | /test/coverage.*
8 | /golint.txt
9 | /govet.txt
10 | /dist
11 | /examples
12 | /identifier/node_modules
13 | /Caddyfile
14 | /3rdparty-LICENSES.md
15 | /identifier-registration.yaml
16 | /scopes.yaml
17 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | run:
2 | modules-download-mode: vendor
3 |
4 | linters-settings:
5 | govet:
6 | check-shadowing: true
7 | settings:
8 | printf:
9 | funcs:
10 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
11 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
12 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
13 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
14 | golint:
15 | min-confidence: 0
16 | gocyclo:
17 | min-complexity: 10
18 | maligned:
19 | suggest-new: true
20 | dupl:
21 | threshold: 100
22 | goconst:
23 | min-len: 2
24 | min-occurrences: 2
25 | misspell:
26 | locale: US
27 | lll:
28 | line-length: 200
29 | goimports:
30 | local-prefixes: stash.kopano.io/kc/kapi
31 | gocritic:
32 | enabled-tags:
33 | - performance
34 | - style
35 | - experimental
36 |
37 | linters:
38 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
39 | disable-all: true
40 | enable:
41 | - bodyclose
42 | - deadcode
43 | - dupl
44 | - errcheck
45 | - funlen
46 | - gochecknoinits
47 | - goconst
48 | - gocritic
49 | - gocyclo
50 | - gofmt
51 | - goimports
52 | - golint
53 | - gosec
54 | - gosimple
55 | - govet
56 | - ineffassign
57 | - interfacer
58 | - lll
59 | - misspell
60 | - nakedret
61 | - scopelint
62 | - staticcheck
63 | - structcheck
64 | - stylecheck
65 | - typecheck
66 | - unconvert
67 | - unparam
68 | - unused
69 | - varcheck
70 |
71 | # don't enable:
72 | # - depguard - until https://github.com/OpenPeeDeeP/depguard/issues/7 gets fixed
73 | # - maligned,prealloc
74 | # - gochecknoglobals
75 |
--------------------------------------------------------------------------------
/.license-ranger.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "mod-vendor",
3 | "header": "# Kopano Konnect 3rd party licenses\n\nCopyright 2019 Kopano and its licensors. See LICENSE.txt for license information. This document contains a list of open source components used in this project.\n\n## Konnect konnectd\n"
4 | }
5 |
--------------------------------------------------------------------------------
/Caddyfile.dev:
--------------------------------------------------------------------------------
1 | # Example Caddyfile to use with https://caddyserver.com
2 | #
3 | # This assumes Konnect is running with identifier on 127.0.0.1:8777. In addition
4 | # for development, the identifier is used directly from webpack-dev-server
5 | # running on 127.0.0.1:3001. Additional examples are included for third party
6 | # login provides which use cookie passthrough backend.
7 |
8 | 0.0.0.0:8443 {
9 | errors stderr
10 | log stdout
11 |
12 | tls self_signed
13 |
14 | # konnect oidc
15 | proxy /.well-known/openid-configuration 127.0.0.1:8777
16 | proxy /konnect/v1/jwks.json 127.0.0.1:8777
17 | proxy /konnect/v1/token 127.0.0.1:8777
18 | proxy /konnect/v1/userinfo 127.0.0.1:8777
19 | proxy /konnect/v1/static 127.0.0.1:8777
20 | proxy /konnect/v1/session 127.0.0.1:8777
21 | proxy /konnect/v1/register 127.0.0.1:8777
22 |
23 | # konnect identifier development via webpack-dev-server
24 | proxy /signin/v1/ 127.0.0.1:3001 {
25 | header_downstream Cache-Control "no-cache, max-age=0, public"
26 | header_downstream Referrer-Policy origin
27 | header_downstream Content-Security-Policy "object-src 'none'; script-src 'self'; base-uri 'none'; frame-ancestors 'none';"
28 | }
29 | proxy /sockjs-node 127.0.0.1:3001 {
30 | websocket
31 | }
32 | proxy /static 127.0.0.1:3001
33 | proxy /signin/v1/identifier/_/ 127.0.0.1:8777 {
34 | transparent
35 | }
36 |
37 | # konnect identifier login area
38 | proxy /signin/ 127.0.0.1:8777 {
39 | transparent
40 | }
41 |
42 | # third party login area provider example
43 | # proxy /provider/simple 127.0.0.1:8999
44 |
45 | # konnect authorize endpoint below third party login area provider
46 | #proxy /provider/simple/konnect/v1/authorize 127.0.0.1:8777 {
47 | # without /provider/simple
48 | # header_upstream X-Forwarded-Prefix /provider/simple
49 | #}
50 |
51 | # konnect cookieserver, start with python3 ./examples/cookieserver.py 8088
52 | #proxy /cookieserver/simple-userinfo 127.0.0.1:8088
53 | }
54 |
--------------------------------------------------------------------------------
/Caddyfile.example:
--------------------------------------------------------------------------------
1 | # Example Caddyfile to use with https://caddyserver.com
2 | #
3 | # This assumes Konnect is running with identifier on 127.0.0.1:8777.
4 |
5 | 0.0.0.0:8443 {
6 | errors stderr
7 | log stdout
8 |
9 | tls self_signed
10 |
11 | # konnect oidc
12 | proxy /.well-known/openid-configuration 127.0.0.1:8777
13 | proxy /konnect/v1/jwks.json 127.0.0.1:8777
14 | proxy /konnect/v1/token 127.0.0.1:8777
15 | proxy /konnect/v1/userinfo 127.0.0.1:8777
16 | proxy /konnect/v1/static 127.0.0.1:8777
17 | proxy /konnect/v1/session 127.0.0.1:8777
18 | proxy /konnect/v1/register 127.0.0.1:8777
19 |
20 | # konnect identifier login area
21 | proxy /signin/ 127.0.0.1:8777 {
22 | transparent
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Dockerfile.build:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Kopano and its licensors
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License, version 3 or
6 | # later, as published by the Free Software Foundation.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Affero General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Affero General Public License
14 | # along with this program. If not, see .
15 | #
16 |
17 | FROM golang:1.14.10-buster
18 |
19 | SHELL ["/bin/bash", "-o", "pipefail", "-c"]
20 |
21 | ARG GOLANGCI_LINT_TAG=v1.23.8
22 | RUN curl -sfL \
23 | https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
24 | sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_TAG}
25 |
26 | RUN GOBIN=/usr/local/bin go get -v \
27 | github.com/tebeka/go2xunit \
28 | github.com/axw/gocov/... \
29 | github.com/AlekSi/gocov-xml \
30 | github.com/wadey/gocovmerge \
31 | && go clean -cache && rm -rf /root/go
32 |
33 | ENV DEBIAN_FRONTEND noninteractive
34 |
35 | RUN apt-get update \
36 | && apt-get install -y --no-install-recommends \
37 | build-essential \
38 | gettext-base \
39 | imagemagick \
40 | python-scour \
41 | nodejs \
42 | && apt-get clean \
43 | && rm -rf /var/lib/apt/lists/*
44 |
45 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
46 | RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
47 | RUN apt-get update \
48 | && apt-get install -y --no-install-recommends \
49 | yarn \
50 | && apt-get clean \
51 | && rm -rf /var/lib/apt/lists/*
52 |
53 | WORKDIR /build
54 |
55 | ENV GOCACHE=/tmp/go-build
56 | ENV GOPATH=""
57 | ENV HOME=/tmp
58 |
59 | CMD ["make", "DATE=reproducible"]
60 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env groovy
2 |
3 | pipeline {
4 | agent {
5 | dockerfile {
6 | filename 'Dockerfile.build'
7 | }
8 | }
9 | stages {
10 | stage('Bootstrap') {
11 | steps {
12 | echo 'Bootstrapping..'
13 | sh 'go version'
14 | sh 'yarn -v'
15 | sh 'node -v'
16 | }
17 | }
18 | stage('Lint') {
19 | steps {
20 | echo 'Linting..'
21 | sh 'make lint-checkstyle GOLINT_ARGS="--new-from-rev=HEAD~ --verbose"'
22 | }
23 | }
24 | stage('Test') {
25 | steps {
26 | echo 'Testing..'
27 | sh 'make test-xml-short'
28 | }
29 | }
30 | stage('Vendor') {
31 | steps {
32 | echo 'Fetching vendor dependencies..'
33 | sh 'make vendor'
34 | }
35 | }
36 | stage('Build') {
37 | steps {
38 | echo 'Building..'
39 | sh 'make DATE=reproducible'
40 | sh './bin/konnectd version && sha256sum ./bin/konnectd'
41 | }
42 | }
43 | stage('Test with coverage') {
44 | steps {
45 | echo 'Testing with coverage..'
46 | sh 'make test-coverage COVERAGE_DIR=test/coverage.jenkins || true'
47 | publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'test/coverage.jenkins', reportFiles: 'coverage.html', reportName: 'Go Coverage Report HTML', reportTitles: ''])
48 | step([$class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'test/coverage.jenkins/coverage.xml', failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false])
49 | }
50 | }
51 | stage('Dist') {
52 | steps {
53 | echo 'Dist..'
54 | sh '$(git diff --stat)'
55 | sh 'test -z "$(git diff --shortstat 2>/dev/null |tail -n1)" && echo "Clean check passed."'
56 | sh 'make check'
57 | sh 'make dist'
58 | }
59 | }
60 | }
61 | post {
62 | always {
63 | junit allowEmptyResults: false, testResults: 'test/tests.xml'
64 |
65 | recordIssues enabledForFailure: true, qualityGates: [[threshold: 100, type: 'TOTAL', unstable: true]], tools: [checkStyle(id: 'golint', name: 'Golint', pattern: 'test/tests.lint.xml'), checkStyle(id: 'eslint', name: 'ESLint', pattern: 'test/tests.eslint.xml')]
66 |
67 | archiveArtifacts 'dist/*.tar.gz'
68 | cleanWs()
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Kopano Konnect
2 | Copyright 2017-2019 Kopano and its licensors
3 |
4 | This product includes software developed at
5 | Kopano (https://kopano.com/).
6 |
--------------------------------------------------------------------------------
/bootstrap/cookie.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package bootstrap
19 |
20 | import (
21 | "fmt"
22 | "net/url"
23 | "strings"
24 | "time"
25 |
26 | "github.com/sirupsen/logrus"
27 |
28 | "stash.kopano.io/kc/konnect/identity"
29 | identityManagers "stash.kopano.io/kc/konnect/identity/managers"
30 | )
31 |
32 | func newCookieIdentityManager(bs *bootstrap, cfg *Config) (identity.Manager, error) {
33 | logger := bs.cfg.Logger
34 |
35 | if bs.authorizationEndpointURI.EscapedPath() == "" {
36 | bs.authorizationEndpointURI.Path = bs.makeURIPath(apiTypeKonnect, "/authorize")
37 | }
38 |
39 | if !strings.HasPrefix(bs.signInFormURI.EscapedPath(), "/") {
40 | return nil, fmt.Errorf("URI path must be absolute")
41 | }
42 |
43 | if cfg.CookieBackendURI == "" {
44 | return nil, fmt.Errorf("cookie backend requires the backend URI as argument")
45 | }
46 | backendURI, backendURIErr := url.Parse(cfg.CookieBackendURI)
47 | if backendURIErr != nil || !backendURI.IsAbs() {
48 | if backendURIErr == nil {
49 | backendURIErr = fmt.Errorf("URI must have a scheme")
50 | }
51 | return nil, fmt.Errorf("invalid backend URI, %v", backendURIErr)
52 | }
53 |
54 | var cookieNames []string
55 | if len(cfg.CookieNames) > 0 {
56 | // TODO(longsleep): Add proper usage help.
57 | cookieNames = cfg.CookieNames
58 | }
59 |
60 | identityManagerConfig := &identity.Config{
61 | SignInFormURI: bs.signInFormURI,
62 |
63 | Logger: logger,
64 |
65 | ScopesSupported: bs.cfg.AllowedScopes,
66 | }
67 |
68 | cookieIdentityManager := identityManagers.NewCookieIdentityManager(identityManagerConfig, backendURI, cookieNames, 30*time.Second, bs.cfg.HTTPTransport)
69 | logger.WithFields(logrus.Fields{
70 | "backend": backendURI,
71 | "signIn": bs.signInFormURI,
72 | "cookies": cookieNames,
73 | }).Infoln("using cookie backed identity manager")
74 |
75 | return cookieIdentityManager, nil
76 | }
77 |
--------------------------------------------------------------------------------
/bootstrap/dummy.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package bootstrap
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/identity"
22 | identityManagers "stash.kopano.io/kc/konnect/identity/managers"
23 | )
24 |
25 | func newDummyIdentityManager(bs *bootstrap) (identity.Manager, error) {
26 | logger := bs.cfg.Logger
27 |
28 | identityManagerConfig := &identity.Config{
29 | Logger: logger,
30 |
31 | ScopesSupported: bs.cfg.AllowedScopes,
32 | }
33 |
34 | sub := "dummy"
35 | dummyIdentityManager := identityManagers.NewDummyIdentityManager(identityManagerConfig, sub)
36 | logger.WithField("sub", sub).Warnln("using dummy identity manager")
37 |
38 | return dummyIdentityManager, nil
39 | }
40 |
--------------------------------------------------------------------------------
/bootstrap/guest.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package bootstrap
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/identity"
22 | identityManagers "stash.kopano.io/kc/konnect/identity/managers"
23 | )
24 |
25 | func newGuestIdentityManager(bs *bootstrap) (identity.Manager, error) {
26 | logger := bs.cfg.Logger
27 |
28 | identityManagerConfig := &identity.Config{
29 | Logger: logger,
30 | }
31 |
32 | guestIdentityManager := identityManagers.NewGuestIdentityManager(identityManagerConfig)
33 |
34 | return guestIdentityManager, nil
35 | }
36 |
--------------------------------------------------------------------------------
/bootstrap/managers.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package bootstrap
19 |
20 | import (
21 | "context"
22 | "fmt"
23 | "time"
24 |
25 | identityAuthorities "stash.kopano.io/kc/konnect/identity/authorities"
26 | identityClients "stash.kopano.io/kc/konnect/identity/clients"
27 | identityManagers "stash.kopano.io/kc/konnect/identity/managers"
28 | "stash.kopano.io/kc/konnect/managers"
29 | codeManagers "stash.kopano.io/kc/konnect/oidc/code/managers"
30 | )
31 |
32 | func newManagers(ctx context.Context, bs *bootstrap) (*managers.Managers, error) {
33 | logger := bs.cfg.Logger
34 |
35 | var err error
36 | mgrs := managers.New()
37 |
38 | // Encryption manager.
39 | encryption, err := identityManagers.NewEncryptionManager(nil)
40 | if err != nil {
41 | return nil, fmt.Errorf("failed to create encryption manager: %v", err)
42 | }
43 |
44 | err = encryption.SetKey(bs.encryptionSecret)
45 | if err != nil {
46 | return nil, fmt.Errorf("invalid --encryption-secret parameter value for encryption: %v", err)
47 | }
48 | mgrs.Set("encryption", encryption)
49 | logger.Infof("encryption set up with %d key size", encryption.GetKeySize())
50 |
51 | // OIDC code manage.
52 | code := codeManagers.NewMemoryMapManager(ctx)
53 | mgrs.Set("code", code)
54 |
55 | // Identifier client registry manager.
56 | clients, err := identityClients.NewRegistry(ctx, bs.issuerIdentifierURI, bs.identifierRegistrationConf, bs.cfg.AllowDynamicClientRegistration, time.Duration(bs.dyamicClientSecretDurationSeconds)*time.Second, logger)
57 | if err != nil {
58 | return nil, fmt.Errorf("failed to create client registry: %v", err)
59 | }
60 | mgrs.Set("clients", clients)
61 |
62 | // Identifier authorities registry manager.
63 | authorities, err := identityAuthorities.NewRegistry(ctx, bs.makeURI(apiTypeSignin, ""), bs.identifierAuthoritiesConf, logger)
64 | if err != nil {
65 | return nil, fmt.Errorf("failed to create authorities registry: %v", err)
66 | }
67 | mgrs.Set("authorities", authorities)
68 |
69 | return mgrs, nil
70 | }
71 |
--------------------------------------------------------------------------------
/cmd/konnectd/env.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2020 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "os"
22 | "strings"
23 | )
24 |
25 | // envOrDefault returns the value of an env-variable or the default if the env-var is not set
26 | func envOrDefault(name string, def string) string {
27 | v := os.Getenv(name)
28 | if v == "" {
29 | return def
30 | }
31 |
32 | return v
33 | }
34 |
35 | // listEnvArg parses an env-arg which has a space separated list as value
36 | func listEnvArg(name string) []string {
37 | list := make([]string, 0)
38 | for _, keyFn := range strings.Split(os.Getenv(name), " ") {
39 | keyFn = strings.TrimSpace(keyFn)
40 | if keyFn != "" {
41 | list = append(list, keyFn)
42 | }
43 | }
44 |
45 | return list
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/konnectd/healthcheck.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "crypto/tls"
23 | "fmt"
24 | "io/ioutil"
25 | "net/http"
26 | "net/url"
27 | "os"
28 | "time"
29 |
30 | "github.com/spf13/cobra"
31 |
32 | "stash.kopano.io/kc/konnect/utils"
33 | )
34 |
35 | func commandHealthcheck() *cobra.Command {
36 | healthcheckCmd := &cobra.Command{
37 | Use: "healthcheck",
38 | Short: "Konnect server health check",
39 | Run: func(cmd *cobra.Command, args []string) {
40 | if err := healthcheck(cmd, args); err != nil {
41 | fmt.Fprintf(os.Stderr, "Error: %v\n", err)
42 | os.Exit(1)
43 | }
44 | },
45 | }
46 |
47 | healthcheckCmd.Flags().String("hostname", defaultListenAddr, "Host and port where konnectd is listening")
48 | healthcheckCmd.Flags().String("path", "/health-check", "URL path and optional parameters to health-check endpoint")
49 | healthcheckCmd.Flags().String("scheme", "http", "URL scheme")
50 | healthcheckCmd.Flags().Bool("insecure", false, "Disable TLS certificate and hostname validation")
51 |
52 | return healthcheckCmd
53 | }
54 |
55 | func healthcheck(cmd *cobra.Command, args []string) error {
56 | ctx := context.Background()
57 |
58 | uri := url.URL{}
59 | uri.Scheme, _ = cmd.Flags().GetString("scheme")
60 | uri.Host, _ = cmd.Flags().GetString("hostname")
61 | uri.Path, _ = cmd.Flags().GetString("path")
62 |
63 | var tlsClientConfig *tls.Config
64 | if insecure, _ := cmd.Flags().GetBool("insecure"); insecure {
65 | tlsClientConfig = utils.InsecureSkipVerifyTLSConfig()
66 | }
67 | client := http.Client{
68 | Timeout: time.Second * 60,
69 | Transport: utils.HTTPTransportWithTLSClientConfig(tlsClientConfig),
70 | }
71 |
72 | request, err := http.NewRequest(http.MethodPost, uri.String(), nil)
73 | if err != nil {
74 | return fmt.Errorf("failed to create healthcheck request: %v", err)
75 | }
76 |
77 | request.Header.Set("Connection", "close")
78 | request.Header.Set("User-Agent", utils.DefaultHTTPUserAgent)
79 | request = request.WithContext(ctx)
80 |
81 | response, err := client.Do(request)
82 | if err != nil {
83 | return fmt.Errorf("healthcheck request failed: %v", err)
84 | }
85 | defer response.Body.Close()
86 |
87 | if response.StatusCode != http.StatusOK {
88 | bodyBytes, _ := ioutil.ReadAll(response.Body)
89 | fmt.Fprintf(os.Stderr, string(bodyBytes))
90 |
91 | return fmt.Errorf("healthcheck failed with status: %v", response.StatusCode)
92 | } else {
93 | fmt.Fprintf(os.Stdout, "healthcheck successful\n")
94 | }
95 |
96 | return nil
97 | }
98 |
--------------------------------------------------------------------------------
/cmd/konnectd/jwk.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "bytes"
22 | "encoding/json"
23 | "fmt"
24 | "os"
25 | "path/filepath"
26 | "strings"
27 |
28 | "github.com/ghodss/yaml"
29 | "github.com/spf13/cobra"
30 | "gopkg.in/square/go-jose.v2"
31 |
32 | "stash.kopano.io/kc/konnect/bootstrap"
33 | )
34 |
35 | func commandJwkFromPem() *cobra.Command {
36 | cmd := &cobra.Command{
37 | Use: "jwk-from-pem [key.pem]",
38 | Short: "Create JSON Web Key from PEM key file",
39 | Run: func(cmd *cobra.Command, args []string) {
40 | if err := jwkFromPem(cmd, args); err != nil {
41 | fmt.Fprintf(os.Stderr, "Error: %v\n", err)
42 | os.Exit(1)
43 | }
44 | },
45 | }
46 |
47 | cmd.Flags().String("kid", "", "Key ID kid")
48 | cmd.Flags().String("use", "sig", "Key usage use (required)")
49 | cmd.Flags().Bool("yaml", false, "Output JWK as YAML")
50 |
51 | return cmd
52 | }
53 |
54 | func jwkFromPem(cmd *cobra.Command, args []string) error {
55 | if len(args) != 1 {
56 | cmd.Help()
57 | os.Exit(2)
58 | }
59 |
60 | kid, _ := cmd.Flags().GetString("kid")
61 | use, _ := cmd.Flags().GetString("use")
62 | asYaml, _ := cmd.Flags().GetBool("yaml")
63 | fn := args[0]
64 |
65 | key, err := func() (interface{}, error) {
66 | signerKid, signer, err := bootstrap.LoadSignerFromFile(fn)
67 | if err == nil {
68 | if kid == "" {
69 | kid = signerKid
70 | }
71 | return signer, nil
72 | }
73 | validatorKid, validator, err := bootstrap.LoadValidatorFromFile(fn)
74 | if err == nil {
75 | if kid == "" {
76 | kid = validatorKid
77 | }
78 | return validator, nil
79 | }
80 | return nil, err
81 | }()
82 | if err != nil {
83 | return fmt.Errorf("failed to load pem file: %v", err)
84 | }
85 |
86 | if kid == "" {
87 | // Use file name as kid if no kid was given.
88 | _, fn := filepath.Split(fn)
89 | kid = strings.TrimSuffix(fn, filepath.Ext(fn))
90 | }
91 |
92 | priv := jose.JSONWebKey{Key: key, KeyID: kid, Use: use}
93 | if !priv.Valid() {
94 | return fmt.Errorf("parsed key is not valid")
95 | }
96 |
97 | privJSON, err := priv.MarshalJSON()
98 | if err != nil {
99 | return fmt.Errorf("error marshaling key as JSON: %v", err)
100 | }
101 |
102 | if asYaml {
103 | privYAML, err := yaml.JSONToYAML(privJSON)
104 | if err != nil {
105 | return fmt.Errorf("error marshalling key as YAML: %v", err)
106 | }
107 | fmt.Println(string(privYAML))
108 | } else {
109 | var prettyPrivJSON bytes.Buffer
110 | err = json.Indent(&prettyPrivJSON, privJSON, "", "\t")
111 | if err != nil {
112 | return fmt.Errorf("error marshalling key as pretty JSON: %v", err)
113 | }
114 | fmt.Println(prettyPrivJSON.String())
115 | }
116 |
117 | return nil
118 | }
119 |
--------------------------------------------------------------------------------
/cmd/konnectd/log.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "os"
22 |
23 | "github.com/sirupsen/logrus"
24 | )
25 |
26 | func newLogger(disableTimestamp bool, logLevelString string) (logrus.FieldLogger, error) {
27 | logLevel, err := logrus.ParseLevel(logLevelString)
28 | if err != nil {
29 | return nil, err
30 | }
31 |
32 | return &logrus.Logger{
33 | Out: os.Stderr,
34 | Formatter: &logrus.TextFormatter{
35 | DisableTimestamp: disableTimestamp,
36 | },
37 | Level: logLevel,
38 | }, nil
39 | }
40 |
--------------------------------------------------------------------------------
/cmd/konnectd/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "os"
23 |
24 | "stash.kopano.io/kc/konnect/cmd"
25 | )
26 |
27 | // Defaults.
28 | const (
29 | defaultListenAddr = "127.0.0.1:8777"
30 | defaultIdentifierClientPath = "./identifier-webapp"
31 | )
32 |
33 | func main() {
34 | cmd.RootCmd.AddCommand(commandServe())
35 | cmd.RootCmd.AddCommand(commandUtils())
36 | cmd.RootCmd.AddCommand(commandHealthcheck())
37 |
38 | if err := cmd.RootCmd.Execute(); err != nil {
39 | fmt.Fprintln(os.Stderr, err.Error())
40 | os.Exit(1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/cmd/konnectd/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "os"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | func commandUtils() *cobra.Command {
27 | jwkCmd := &cobra.Command{
28 | Use: "utils",
29 | Short: "Konnect related utilities",
30 | Run: func(cmd *cobra.Command, args []string) {
31 | cmd.Help()
32 | os.Exit(2)
33 | },
34 | }
35 |
36 | jwkCmd.AddCommand(commandJwkFromPem())
37 |
38 | return jwkCmd
39 | }
40 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "os"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | // RootCmd provides the commandline parser root.
27 | var RootCmd = &cobra.Command{
28 | Use: "konnectd",
29 | Run: func(cmd *cobra.Command, args []string) {
30 | cmd.Help()
31 | os.Exit(2)
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "fmt"
22 | "runtime"
23 |
24 | "github.com/spf13/cobra"
25 |
26 | "stash.kopano.io/kc/konnect/version"
27 | )
28 |
29 | // CommandVersion provides the commandline implementation for version.
30 | func CommandVersion() *cobra.Command {
31 | versionCmd := &cobra.Command{
32 | Use: "version",
33 | Short: "Print the version and exit",
34 | Run: func(cmd *cobra.Command, args []string) {
35 | fmt.Printf(`Version : %s
36 | Build date : %s
37 | Built with : %s %s/%s
38 | `,
39 | version.Version, version.BuildDate, runtime.Version(), runtime.GOOS, runtime.GOARCH)
40 | },
41 | }
42 |
43 | return versionCmd
44 | }
45 |
46 | func init() {
47 | RootCmd.AddCommand(CommandVersion())
48 | }
49 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package config
19 |
20 | import (
21 | "net"
22 | "net/http"
23 |
24 | "github.com/sirupsen/logrus"
25 | )
26 |
27 | // Config defines a Server's configuration settings.
28 | type Config struct {
29 | ListenAddr string
30 |
31 | WithMetrics bool
32 |
33 | Logger logrus.FieldLogger
34 | HTTPTransport http.RoundTripper
35 |
36 | TrustedProxyIPs []*net.IP
37 | TrustedProxyNets []*net.IPNet
38 |
39 | AllowedScopes []string
40 | AllowClientGuests bool
41 | AllowDynamicClientRegistration bool
42 | }
43 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package konnect
19 |
20 | import (
21 | "context"
22 |
23 | "github.com/dgrijalva/jwt-go"
24 | )
25 |
26 | // key is an unexported type for keys defined in this package.
27 | // This prevents collisions with keys defined in other packages.
28 | type key int
29 |
30 | // claimsKey is the key for claims in contexts. It is
31 | // unexported; clients use konnect.NewClaimsContext and
32 | // connect.FromClaimsContext instead of using this key directly.
33 | var claimsKey key
34 |
35 | // NewClaimsContext returns a new Context that carries value auth.
36 | func NewClaimsContext(ctx context.Context, claims jwt.Claims) context.Context {
37 | return context.WithValue(ctx, claimsKey, claims)
38 | }
39 |
40 | // FromClaimsContext returns the AuthRecord value stored in ctx, if any.
41 | func FromClaimsContext(ctx context.Context) (jwt.Claims, bool) {
42 | claims, ok := ctx.Value(claimsKey).(jwt.Claims)
43 | return claims, ok
44 | }
45 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | // Package konnect is a Go implementation of an OpenID Connect server with
19 | // flexibale authorization and authentication backends and consent screen.
20 | //
21 | // See README.md for more info.
22 | package konnect // import "stash.kopano.io/kc/konnect"
23 |
--------------------------------------------------------------------------------
/encryption/encryption.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package encryption
19 |
20 | import (
21 | "fmt"
22 |
23 | "golang.org/x/crypto/nacl/secretbox"
24 | )
25 |
26 | const (
27 | // KeySize is the size of the keys created by GenerateKey()
28 | KeySize = 32
29 | // NonceSize is the size of the nonces created by GenerateNonce()
30 | NonceSize = 24
31 | )
32 |
33 | // Encrypt generates a random nonce and encrypts the input using nacl.secretbox
34 | // package. We store the nonce in the first 24 bytes of the encrypted text.
35 | func Encrypt(msg []byte, key *[KeySize]byte) ([]byte, error) {
36 | nonce, err := GenerateNonce()
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | return encryptWithNonce(msg, nonce, key)
42 | }
43 |
44 | func encryptWithNonce(msg []byte, nonce *[NonceSize]byte, key *[KeySize]byte) ([]byte, error) {
45 | encrypted := secretbox.Seal(nonce[:], msg, nonce, key)
46 | return encrypted, nil
47 | }
48 |
49 | // Decrypt extracts the nonce from the encrypted text, and attempts to decrypt
50 | // with nacl.box.
51 | func Decrypt(msg []byte, key *[KeySize]byte) ([]byte, error) {
52 | if len(msg) < (NonceSize + secretbox.Overhead) {
53 | return nil, fmt.Errorf("wrong length of ciphertext")
54 | }
55 |
56 | var nonce [NonceSize]byte
57 | copy(nonce[:], msg[:NonceSize])
58 | decrypted, ok := secretbox.Open(nil, msg[NonceSize:], &nonce, key)
59 | if !ok {
60 | return nil, fmt.Errorf("decryption failed")
61 | }
62 |
63 | return decrypted, nil
64 | }
65 |
--------------------------------------------------------------------------------
/encryption/encryption_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package encryption
19 |
20 | import (
21 | "bytes"
22 | "encoding/hex"
23 | "testing"
24 | )
25 |
26 | var (
27 | defaultSecretKey [KeySize]byte
28 | defaultNonce [NonceSize]byte
29 | defaultPlainText []byte
30 | defaultEncryptedText []byte
31 | )
32 |
33 | func init() {
34 | secretKey, _ := hex.DecodeString("0f9c41164bab0d26fd4e1d50ea7323b294a5cd4c5bc79dbfd667b0d870bdd11e")
35 | copy(defaultSecretKey[:], secretKey[:KeySize])
36 |
37 | nonce, _ := hex.DecodeString("87e0e8c89c58eda1ced9aa829e1f32d8837c5f9660717a49")
38 | copy(defaultNonce[:], nonce[:NonceSize])
39 |
40 | defaultPlainText = []byte("We are Kopano, and we empower you to have choice to use multiple ways to communicate with others, be it email, video meetings or chat.")
41 |
42 | defaultEncryptedText, _ = hex.DecodeString("87e0e8c89c58eda1ced9aa829e1f32d8837c5f9660717a4947526753dbe3a493d2c97961591c0d05dd7525ca34165ac1aeb0950789cfd3f8a8c8e981f564494c0a8cb039bf14d844af6dfb107af432160d58e3ff544f28c00f1b27ebcc12424b19bc2325fdb31513da4b45731adc004b05b3fe821038f77097bffb4507e9d7a79b86cb1318e1bfe4ab7a84d01888daa916470238fe71d7d83c16fd6066d672cd491b29f590fcfada5a0c89386d31")
43 | }
44 |
45 | func TestGenerateNonce(t *testing.T) {
46 | nonce, err := GenerateNonce()
47 | if err != nil {
48 | t.Fatal(err)
49 | }
50 |
51 | if len(nonce) != NonceSize {
52 | t.Fatalf("nonce has wrong size: got %v want %v", len(nonce), NonceSize)
53 | }
54 | }
55 |
56 | func TestGenerateKey(t *testing.T) {
57 | secretKey, err := GenerateKey()
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 |
62 | if len(secretKey) != KeySize {
63 | t.Fatalf("secret key has wrong size: got %v want %v", len(secretKey), KeySize)
64 | }
65 | }
66 |
67 | func TestEncryptWithNonce(t *testing.T) {
68 | msg := []byte(defaultPlainText)
69 | encrypted, err := encryptWithNonce(msg, &defaultNonce, &defaultSecretKey)
70 | if err != nil {
71 | t.Fatal(err)
72 | }
73 |
74 | if !bytes.Equal(encrypted, defaultEncryptedText) {
75 | t.Fatalf("encrypted text does not match expected value, %x", encrypted)
76 | }
77 | }
78 |
79 | func TestEncrypt(t *testing.T) {
80 | msg := []byte(defaultPlainText)
81 | encrypted, err := Encrypt(msg, &defaultSecretKey)
82 | if err != nil {
83 | t.Fatal(err)
84 | }
85 |
86 | if bytes.Equal(encrypted, defaultEncryptedText) {
87 | t.Fatal("encrypted text does not seem to have a nonce")
88 | }
89 | }
90 |
91 | func TestDecrypt(t *testing.T) {
92 | decrypted, err := Decrypt(defaultEncryptedText, &defaultSecretKey)
93 | if err != nil {
94 | t.Fatal(err)
95 | }
96 |
97 | if !bytes.Equal(decrypted, defaultPlainText) {
98 | t.Fatalf("decrypted text does not match expected value, got %v", decrypted)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/encryption/generate.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package encryption
19 |
20 | import (
21 | "crypto/rand"
22 | "io"
23 | )
24 |
25 | // GenerateKey generates a new random secret key.
26 | func GenerateKey() (*[KeySize]byte, error) {
27 | key := new([KeySize]byte)
28 | _, err := io.ReadFull(rand.Reader, key[:])
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | return key, nil
34 | }
35 |
36 | // GenerateNonce creates a new random nonce.
37 | func GenerateNonce() (*[NonceSize]byte, error) {
38 | nonce := new([NonceSize]byte)
39 | _, err := io.ReadFull(rand.Reader, nonce[:])
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | return nonce, nil
45 | }
46 |
--------------------------------------------------------------------------------
/examples/coookieserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from http.server import BaseHTTPRequestHandler, HTTPServer
4 | from http import cookies
5 | import json
6 |
7 | # User table.
8 | users = {}
9 |
10 |
11 | def addUsers(l):
12 | for u in l:
13 | users[u.get("sub")] = u
14 |
15 | # Add numeric user ID.
16 | u["id"] = len(users)
17 |
18 |
19 | addUsers([
20 | {
21 | "sub": "arthur",
22 | "name": "Arthur Dent",
23 | "email": "arthur@earth.local",
24 | },
25 | {
26 | "sub": "trillian",
27 | "name": "Trillian",
28 | "email": "trillian@galaxy.local",
29 | },
30 | {
31 | "sub": "ford",
32 | "name": "Ford Prefect",
33 | "email": "ford@betelgeuse.local",
34 | }
35 | ])
36 |
37 |
38 | class Handler(BaseHTTPRequestHandler):
39 | def do_POST(self):
40 | cookie = cookies.SimpleCookie()
41 | cookie.load(self.headers.get("Cookie", ""))
42 | id = cookie.get("minioidc-simple", None)
43 | if id is None:
44 | self.send_response(401)
45 | self.end_headers()
46 | return
47 |
48 | user = users.get(id.value, None)
49 | if user is None:
50 | self.send_response(403)
51 | self.end_headers()
52 | return
53 |
54 | self.send_response(202)
55 | self.send_header('Content-Type', 'application/json')
56 | self.end_headers()
57 |
58 | self.wfile.write(json.dumps(user, indent=4, sort_keys=True).encode())
59 | return
60 |
61 |
62 | def run(server_class=HTTPServer, handler_class=Handler, port=8080):
63 | address = ('127.0.0.1', port)
64 | server = server_class(address, handler_class)
65 | print('Starting HTTP server ...')
66 | server.serve_forever()
67 |
68 |
69 | if __name__ == '__main__':
70 | from sys import argv
71 |
72 | if len(argv) == 2:
73 | run(port=int(argv[1]))
74 | else:
75 | run()
76 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module stash.kopano.io/kc/konnect
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/beevik/etree v1.1.0
7 | github.com/crewjam/httperr v0.2.0
8 | github.com/crewjam/saml v0.4.3
9 | github.com/deckarep/golang-set v1.7.1
10 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
11 | github.com/ghodss/yaml v1.0.0
12 | github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
13 | github.com/go-ldap/ldap/v3 v3.1.7
14 | github.com/golang/protobuf v1.3.4 // indirect
15 | github.com/google/go-querystring v1.0.0
16 | github.com/gorilla/mux v1.7.4
17 | github.com/gorilla/schema v1.1.0
18 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
19 | github.com/longsleep/go-metrics v0.0.0-20191013204616-cddea569b0ea
20 | github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103
21 | github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
22 | github.com/pkg/errors v0.9.1 // indirect
23 | github.com/prometheus/client_golang v1.5.0
24 | github.com/prometheus/procfs v0.0.10 // indirect
25 | github.com/rs/cors v1.7.0
26 | github.com/russellhaering/goxmldsig v1.1.0
27 | github.com/satori/go.uuid v1.2.0
28 | github.com/sirupsen/logrus v1.4.2
29 | github.com/spf13/cobra v0.0.6
30 | github.com/spf13/pflag v1.0.5 // indirect
31 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
32 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a
33 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
34 | golang.org/x/text v0.3.2 // indirect
35 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0
36 | gopkg.in/square/go-jose.v2 v2.4.1
37 | gopkg.in/yaml.v2 v2.2.8
38 | stash.kopano.io/kgol/kcc-go/v5 v5.0.1
39 | stash.kopano.io/kgol/ksurveyclient-go v0.6.0
40 | stash.kopano.io/kgol/oidc-go v0.3.1
41 | stash.kopano.io/kgol/rndm v1.1.0
42 | )
43 |
44 | replace github.com/crewjam/httperr => github.com/crewjam/httperr v0.2.0
45 |
46 | replace github.com/mattermost/xml-roundtrip-validator => github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e
47 |
--------------------------------------------------------------------------------
/identifier-registration.yaml.in:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # OpenID Connect client registry.
4 | clients:
5 | # - id: playground.js
6 | # name: OIDC Playground
7 | # application_type: web
8 | # redirect_uris:
9 | # - https://my-host:8509/
10 | # origins:
11 | # - https://my-host:8509
12 |
13 | # - id: playground-trusted.js
14 | # name: Trusted OIDC Playground
15 | # trusted: yes
16 | # application_type: web
17 | # redirect_uris:
18 | # - https://my-host:8509/
19 | # origins:
20 | # - https://my-host:8509
21 |
22 | # - id: playground-trusted.js
23 | # name: Trusted Insecure OIDC Playground
24 | # trusted: yes
25 | # application_type: web
26 | # insecure: yes
27 |
28 | # - id: client-with-keys
29 | # secret: super
30 | # application_type: native
31 | # redirect_uris:
32 | # - http://localhost
33 | # trusted_scopes:
34 | # - konnect/guestok
35 | # - kopano/kwm
36 | # jwks:
37 | # keys:
38 | # - kty: EC
39 | # use: sig
40 | # kid: client-with-keys-key-1
41 | # crv: P-256
42 | # x: RTZpWoRbjwX1YavmSHVBj6Cy3Yzdkkp6QLvTGB22D0c
43 | # y: jeavjwcX0xlDSchFcBMzXSU7wGs2VPpNxWCwmxFvmF0
44 | # request_object_signing_alg: ES256
45 |
46 | # - id: first
47 | # secret: lala
48 | # application_type: native
49 | # redirect_uris:
50 | # - my://app
51 |
52 | # - id: second
53 | # secret: lulu
54 | # application_type: native
55 | # redirect_uris:
56 | # - http://localhost
57 |
58 | # External authority registry.
59 | authorities:
60 | # - id: my-univention-oidc
61 | # name: Univention
62 | # client_id: kopano-konnect
63 | # authority_type: oidc
64 | # jwks:
65 | # keys:
66 | # - kty: EC
67 | # use: sig
68 | # kid: example-key-1
69 | # crv: P-256
70 | # x: RTZpWoRbjwX1YavmSHVBj6Cy3Yzdkkp6QLvTGB22D0c
71 | # y: jeavjwcX0xlDSchFcBMzXSU7wGs2VPpNxWCwmxFvmF0
72 | # default: yes
73 | # authorization_endpoint: https://my-univention/signin/v1/identifier/_/authorize
74 | # response_type: id_token
75 | # scopes:
76 | # - openid
77 | # - profile
78 | # identity_claim_name: preferred_username
79 | # identity_aliases:
80 | # external-user-a: local-user-a
81 | # external-user-b: local-user-b
82 | # identity_alias_required: true
83 |
84 | # - id: my-univention-saml2
85 | # name: Univention
86 | # entity_id: kopano-konnect
87 | # authority_type: saml2
88 | # default: yes
89 | # trusted: yes
90 | # discover: yes
91 | # metadata_endpoint: https://my-univention/simplesamlphp/saml2/idp/metadata.php
92 | # identity_claim_name: uid
93 | # identity_alias_required: false
94 | # end_session_enabled: true
95 |
--------------------------------------------------------------------------------
/identifier/.env:
--------------------------------------------------------------------------------
1 | PORT=3001
2 | HOST=127.0.0.1
3 | BROWSER=none
4 | REACT_APP_KOPANO_BUILD=0.0.0-dev-env
5 | INLINE_RUNTIME_CHUNK=false
6 | EXTEND_ESLINT=true
7 |
--------------------------------------------------------------------------------
/identifier/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*
2 | node_modules/*
3 |
4 |
--------------------------------------------------------------------------------
/identifier/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["react-intl-format"],
3 | "extends": ["plugin:react/recommended", "plugin:jest/recommended"],
4 | "settings": {
5 | "react": {
6 | "version": "detect"
7 | }
8 | },
9 | "parser": "babel-eslint",
10 | "rules": {
11 | "react-intl-format/missing-formatted-message": [
12 | "error",
13 | {
14 | "noTrailingWhitespace": false,
15 | "ignoreLinks": false
16 | }
17 | ],
18 | "react-intl-format/missing-attribute": [
19 | "error",
20 | {
21 | "noTrailingWhitespace": false,
22 | "noSpreadOperator": true
23 | }
24 | ],
25 | "react-intl-format/missing-values": [
26 | "error"
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/identifier/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.yarninstall
6 |
7 | # testing
8 | /coverage
9 | .eslintcache
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # i18n
26 | *.mo
27 | /src/locales/*.json
28 | /i18n/src
29 |
--------------------------------------------------------------------------------
/identifier/Makefile:
--------------------------------------------------------------------------------
1 | # Tools
2 |
3 | YARN ?= yarn
4 |
5 | # Variables
6 |
7 | VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2>/dev/null | sed 's/^v//' || \
8 | cat $(CURDIR)/../.version 2> /dev/null || echo 0.0.0-unreleased)
9 |
10 | # Build
11 |
12 | .PHONY: all
13 | all: build
14 |
15 | .PHONY: build
16 | build: vendor | src i18n ; $(info building identifier Webapp ...) @
17 | @rm -rf build
18 |
19 | REACT_APP_KOPANO_BUILD="${VERSION}" $(YARN) run build
20 |
21 | .PHONY: src
22 | src:
23 | @$(MAKE) -C src
24 |
25 | .PHONY: i18n
26 | i18n: vendor
27 | @$(MAKE) -C i18n
28 |
29 | .PHONY: lint
30 | lint: vendor ; $(info running eslint ...) @
31 | @$(YARN) eslint . --cache && echo "eslint: no lint errors"
32 |
33 | .PHONY: lint-checkstyle
34 | lint-checkstyle: vendor ; $(info running eslint checkstyle ...) @
35 | @mkdir -p ../test
36 | $(YARN) eslint -f checkstyle -o ../test/tests.eslint.xml . || true
37 |
38 | # Yarn
39 |
40 | .PHONY: vendor
41 | vendor: .yarninstall
42 |
43 | .yarninstall: package.json ; $(info getting depdencies with yarn ...) @
44 | @$(YARN) install --silent
45 | @touch $@
46 |
47 | # Stuff
48 |
49 | .PHONY: licenses
50 | licenses:
51 | echo "## Konnect identifier webapp\n"
52 | @$(YARN) run -s licenses
53 |
54 | .PHONY: clean ; $(info cleaning identifier Webapp ...) @
55 | clean:
56 | $(YARN) cache clean
57 | @rm -rf build
58 | @rm -rf node_modules
59 | @rm -f .yarninstall
60 |
61 | @$(MAKE) -C src clean
62 |
63 | .PHONY: version
64 | version:
65 | @echo $(VERSION)
66 |
--------------------------------------------------------------------------------
/identifier/README.md:
--------------------------------------------------------------------------------
1 | # Kopano Konnect Identifier
2 |
3 | Web app for browser sign-in, sign-out and account management.
4 |
--------------------------------------------------------------------------------
/identifier/backends/backend.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package backends
19 |
20 | import (
21 | "context"
22 |
23 | "stash.kopano.io/kc/konnect/identifier/meta/scopes"
24 | "stash.kopano.io/kc/konnect/identity"
25 | )
26 |
27 | // A Backend is an identifier Backend providing functionality to logon and to
28 | // fetch user meta data.
29 | type Backend interface {
30 | RunWithContext(context.Context) error
31 |
32 | Logon(ctx context.Context, audience string, username string, password string) (success bool, userID *string, sessionRef *string, user UserFromBackend, err error)
33 | GetUser(ctx context.Context, userID string, sessionRef *string) (user UserFromBackend, err error)
34 |
35 | ResolveUserByUsername(ctx context.Context, username string) (user UserFromBackend, err error)
36 |
37 | RefreshSession(ctx context.Context, userID string, sessionRef *string, claims map[string]interface{}) error
38 | DestroySession(ctx context.Context, sessionRef *string) error
39 |
40 | UserClaims(userID string, authorizedScopes map[string]bool) map[string]interface{}
41 | ScopesSupported() []string
42 | ScopesMeta() *scopes.Scopes
43 |
44 | Name() string
45 | }
46 |
47 | // UserFromBackend are users as provided by backends which can have additional
48 | // claims together with a user name.
49 | type UserFromBackend interface {
50 | identity.UserWithUsername
51 | BackendClaims() map[string]interface{}
52 | }
53 |
--------------------------------------------------------------------------------
/identifier/backends/kc/consts.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package kc
19 |
20 | // Define scopes supported by KC.
21 | const (
22 | ScopeKopanoGC = "kopano/gc"
23 | )
24 |
25 | // Define claims supported by KC.
26 | const (
27 | KopanoGCIDClaim = "kopano/gc/id"
28 | )
29 |
--------------------------------------------------------------------------------
/identifier/backends/ldap/const.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package ldap
19 |
20 | // Define some known LDAP attribute descriptors.
21 | const (
22 | AttributeDN = "dn"
23 | AttributeLogin = "uid"
24 | AttributeEmail = "mail"
25 | AttributeName = "cn"
26 | AttributeFamilyName = "sn"
27 | AttributeGivenName = "givenName"
28 | AttributeUUID = "uuid"
29 | )
30 |
31 | // Additional mappable virtual attributes.
32 | const (
33 | AttributeNumericUID = "konnectNumericID"
34 | )
35 |
36 | // Define our known LDAP attribute value types.
37 | const (
38 | AttributeValueTypeText = "text"
39 | AttributeValueTypeBinary = "binary"
40 | AttributeValueTypeUUID = "uuid"
41 | )
42 |
--------------------------------------------------------------------------------
/identifier/claims.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | // Additional claims as used by the identifier in its own tokens.
21 | const (
22 | SessionIDClaim = "sid"
23 | LogonRefClaim = "lref"
24 | ExternalAuthorityIDClaim = "eaid"
25 | )
26 |
27 | // History claims previously used by the identifier in its own tokens.
28 | const (
29 | ObsoleteUserClaimsClaim = "claims"
30 | )
31 |
--------------------------------------------------------------------------------
/identifier/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | import (
21 | "net/url"
22 |
23 | "stash.kopano.io/kc/konnect/config"
24 | "stash.kopano.io/kc/konnect/identifier/backends"
25 | )
26 |
27 | // Config defines a Server's configuration settings.
28 | type Config struct {
29 | Config *config.Config
30 |
31 | BaseURI *url.URL
32 | LogonCookieName string
33 | ScopesConf string
34 |
35 | PathPrefix string
36 | StaticFolder string
37 | WebAppDisabled bool
38 |
39 | AuthorizationEndpointURI *url.URL
40 | SignedOutEndpointURI *url.URL
41 |
42 | Backend backends.Backend
43 | }
44 |
--------------------------------------------------------------------------------
/identifier/flows.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | const (
21 | // FlowOIDC is the string value for the oidc flow.
22 | FlowOIDC = "oidc"
23 | // FlowOAuth is the string value for the oauth flow.
24 | FlowOAuth = "oauth"
25 | // FlowConsent is the string value for the consent flow.
26 | FlowConsent = "consent"
27 | )
28 |
--------------------------------------------------------------------------------
/identifier/i18n/Makefile:
--------------------------------------------------------------------------------
1 | # Tools
2 |
3 | YARN ?= yarn
4 | MSGMERGE ?= msgmerge
5 | MSGFMT ?= msgfmt
6 |
7 | # Variables
8 |
9 | POT = konnect-identifier.pot
10 | POS = $(wildcard *.po)
11 |
12 | # Translations
13 |
14 | .PHONY: build
15 | build: json
16 |
17 | .PHONY: extract
18 | extract: pot
19 |
20 | .PHONY: i18n/src/messages.json
21 | i18n/src/messages.json:
22 | $(YARN) react-intl-cra './src/**/*.{js,jsx}' -o $@
23 |
24 | .PHONY: pot
25 | pot: i18n/src/messages.json
26 | $(YARN) rip json2pot './i18n/src/**/*.json' \
27 | -o ./i18n/$(POT) \
28 | -c 'id'
29 |
30 | .PHONY: json
31 | json: i18n/src/messages.json
32 | $(YARN) rip po2json './i18n/*.po' \
33 | -m './i18n/src/**/*.json' \
34 | -o './src/locales/' \
35 | -c 'id' \
36 | --indentation=2
37 |
38 | .PHONY: merge
39 | merge: $(POS)
40 |
41 | .PHONY: stats
42 | stats:
43 | $(foreach po, $(POS), $(shell $(MSGFMT) -v --statistics $(po)))
44 | @- true
45 |
46 | $(POS): FORCE $(POT)
47 | $(MSGMERGE) -U \
48 | --backup=none \
49 | --no-wrap \
50 | $@ $(POT)
51 |
52 | FORCE:
53 |
--------------------------------------------------------------------------------
/identifier/meta/meta.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package meta
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/identifier/meta/scopes"
22 | )
23 |
24 | // Meta is a container to hold identifier meta data which can be requested by
25 | // clients.
26 | type Meta struct {
27 | Scopes *scopes.Scopes `json:"scopes"`
28 | }
29 |
--------------------------------------------------------------------------------
/identifier/meta/scopes/definition.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package scopes
19 |
20 | // A Definition contains the meta data for a single scope.
21 | type Definition struct {
22 | Priority int `json:"priority" yaml:"priority"`
23 | Description string `json:"description,omitempty" yaml:"description"`
24 | ID string `json:"id,omitempty"`
25 | }
26 |
--------------------------------------------------------------------------------
/identifier/modes.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | const (
21 | // ModeLogonUsernameEmptyPasswordCookie is the logon mode which requires a
22 | // username which matches the currently signed in user in the cookie and an
23 | // empty password.
24 | ModeLogonUsernameEmptyPasswordCookie = "0"
25 | // ModeLogonUsernamePassword is the logon mode which requires a username
26 | // and a password.
27 | ModeLogonUsernamePassword = "1"
28 | )
29 |
30 | const (
31 | // MustBeSignedIn is a authorize mode which tells the authorization code,
32 | // that it is expected to have a signed in user and everything else should
33 | // be treated as error.
34 | MustBeSignedIn = "must"
35 | )
36 |
37 | const (
38 | // StateModeEndSession is a state mode which selects end session specific
39 | // actions when processing state requests.
40 | StateModeEndSession = "0"
41 | )
42 |
--------------------------------------------------------------------------------
/identifier/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "identifier",
3 | "version": "1.0.0",
4 | "private": true,
5 | "homepage": ".",
6 | "dependencies": {
7 | "@material-ui/core": "^4.8.0",
8 | "@material-ui/icons": "^4.5.0",
9 | "axios": "^0.19.2",
10 | "classnames": "^2.2.6",
11 | "kpop": "https://download.kopano.io/community/kapp:/kpop-2.2.0.tgz",
12 | "query-string": "^5.0.1",
13 | "react": "^16.8.0",
14 | "react-dom": "^16.8.0",
15 | "react-intl": "^2.4.0",
16 | "react-loadable": "^5.3.1",
17 | "react-redux": "^5.0.6",
18 | "react-router": "^5.0.0",
19 | "react-router-dom": "5.0.0",
20 | "react-scripts": "3.3.1",
21 | "redux": "^3.7.2",
22 | "redux-logger": "^3.0.6",
23 | "redux-thunk": "^2.2.0",
24 | "render-if": "^0.1.1",
25 | "typeface-roboto": "^0.0.54"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build && rm -f build/service-worker.js",
30 | "test": "react-scripts test --env=jsdom",
31 | "eject": "react-scripts eject",
32 | "licenses": "NODE_PATH=./node_modules node ../scripts/js-license-ranger.js",
33 | "deduplicate": "yarn-deduplicate -s fewer yarn.lock",
34 | "analyze": "source-map-explorer 'build/static/js/*.js'"
35 | },
36 | "devDependencies": {
37 | "@babel/runtime": "7.0.0-beta.55",
38 | "eslint": ">=5",
39 | "eslint-plugin-i18n-text": "^1.0.0",
40 | "eslint-plugin-jest": "^22.5.1",
41 | "eslint-plugin-react": "^7.10.0",
42 | "eslint-plugin-react-intl-format": "^1.1.6",
43 | "prop-types": "^15.6.2",
44 | "react-intl-cra": "^0.3.3",
45 | "react-intl-po": "^2.2.2",
46 | "source-map-explorer": "^1.8.0",
47 | "yarn-deduplicate": "^1.1.1"
48 | },
49 | "resolutions": {
50 | "**/react": "16.8.6",
51 | "**/react-dom": "16.8.6",
52 | "**/@material-ui/core": "4.8.0",
53 | "**/@material-ui/icons": "4.5.1",
54 | "**/react-intl": "2.9.0",
55 | "**/create-react-context": "0.3.0"
56 | },
57 | "jest": {
58 | "collectCoverageFrom": [
59 | "src/**/*.js"
60 | ],
61 | "transformIgnorePatterns": [
62 | "node_modules/?!(kpop)"
63 | ]
64 | },
65 | "browserslist": [
66 | ">0.2%",
67 | "not dead",
68 | "not ie <= 11",
69 | "not op_mini all"
70 | ]
71 | }
72 |
--------------------------------------------------------------------------------
/identifier/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Kopano Sign in
10 |
11 |
12 |
15 |
19 |
20 | aABb
21 |
22 |
23 |
--------------------------------------------------------------------------------
/identifier/public/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kopano-dev/konnect/52c7ab10ee1f2a39323b3f9db1514e0433138e2a/identifier/public/static/favicon.ico
--------------------------------------------------------------------------------
/identifier/src/Main.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | import { BrowserRouter } from 'react-router-dom';
6 |
7 | import { withStyles } from '@material-ui/core/styles';
8 |
9 | import { enhanceBodyBackground } from './utils';
10 | import Routes from './Routes';
11 |
12 | // Trigger loading of background image.
13 | enhanceBodyBackground();
14 |
15 | const styles = () => ({
16 | root: {
17 | position: 'relative',
18 | display: 'flex',
19 | flex: 1
20 | }
21 | });
22 |
23 | class App extends PureComponent {
24 | render() {
25 | const { classes, hello, pathPrefix } = this.props;
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | reload(event) {
37 | event.preventDefault();
38 |
39 | window.location.reload();
40 | }
41 | }
42 |
43 | App.propTypes = {
44 | classes: PropTypes.object.isRequired,
45 |
46 | hello: PropTypes.object,
47 | updateAvailable: PropTypes.bool.isRequired,
48 | pathPrefix: PropTypes.string.isRequired
49 | };
50 |
51 | const mapStateToProps = (state) => {
52 | const { hello, updateAvailable, pathPrefix } = state.common;
53 |
54 | return {
55 | hello,
56 | updateAvailable,
57 | pathPrefix
58 | };
59 | };
60 |
61 | export default connect(mapStateToProps)(withStyles(styles)(App));
62 |
--------------------------------------------------------------------------------
/identifier/src/Main.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import { Provider } from 'react-redux';
5 |
6 | import store from './store';
7 | import App from './Main';
8 |
9 | it('renders without crashing', () => {
10 | const div = document.createElement('div');
11 | ReactDOM.render(, div);
12 | });
13 |
--------------------------------------------------------------------------------
/identifier/src/Makefile:
--------------------------------------------------------------------------------
1 | all: images
2 |
3 | .PHONY: images
4 | images:
5 | @$(MAKE) -C images
6 |
7 | clean:
8 | @$(MAKE) -C images clean
9 |
--------------------------------------------------------------------------------
/identifier/src/Routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Route, Switch } from 'react-router-dom';
5 | import AsyncComponent from 'kpop/es/AsyncComponent';
6 |
7 | import PrivateRoute from './components/PrivateRoute';
8 |
9 | const AsyncLogin = AsyncComponent(() =>
10 | import(/* webpackChunkName: "containers-login" */ './containers/Login'));
11 | const AsyncWelcome = AsyncComponent(() =>
12 | import(/* webpackChunkName: "containers-welcome" */ './containers/Welcome'));
13 | const AsyncGoodbye = AsyncComponent(() =>
14 | import(/* webpackChunkName: "containers-goodbye" */ './containers/Goodbye'));
15 |
16 | const Routes = ({ hello }) => (
17 |
18 |
24 |
29 |
33 |
34 | );
35 |
36 | Routes.propTypes = {
37 | hello: PropTypes.object
38 | };
39 |
40 | export default Routes;
41 |
--------------------------------------------------------------------------------
/identifier/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const RECEIVE_ERROR = 'RECEIVE_ERROR';
2 |
3 | export const RESET_HELLO = 'RESET_HELLO';
4 | export const EXECUTE_HELLO = 'EXECUTE_HELLO';
5 | export const RECEIVE_HELLO = 'RECEIVE_HELLO';
6 |
7 | export const RECEIVE_VALIDATE_LOGON = 'RECEIVE_VALIDATE_LOGON';
8 | export const REQUEST_LOGON = 'REQUEST_LOGON';
9 | export const EXECUTE_LOGON = 'EXECUTE_LOGON';
10 | export const RECEIVE_LOGON = 'RECEIVE_LOGON';
11 | export const UPDATE_INPUT = 'UPDATE_INPUT';
12 |
13 | export const REQUEST_CONSENT_ALLOW = 'REQUEST_CONSENT_ALLOW';
14 | export const REQUEST_CONSENT_CANCEL = 'REQUEST_CONSENT_CANCEL';
15 | export const EXECUTE_CONSENT = 'EXECUTE_CONSENT';
16 | export const RECEIVE_CONSENT = 'RECEIVE_CONSENT';
17 |
18 | export const REQUEST_LOGOFF = 'REQUEST_LOGOFF';
19 | export const EXECUTE_LOGOFF = 'EXECUTE_LOGOFF';
20 | export const RECEIVE_LOGOFF = 'RECEIVE_LOGOFF';
21 |
22 | export const SERVICE_WORKER_NEW_CONTENT = 'SERVICE_WORKER_NEW_CONTENT';
23 | export const SERVICE_WORKER_READY = 'SERVICE_WORKER_READY';
24 | export const SERVICE_WORKER_ERROR = 'SERVICE_WORKER_ERROR';
25 | export const SERVICE_WORKER_OFFLINE = 'SERVICE_WORKER_OFFLINE';
26 |
--------------------------------------------------------------------------------
/identifier/src/actions/utils.js:
--------------------------------------------------------------------------------
1 | import {
2 | ExtendedError,
3 | ERROR_HTTP_NETWORK_ERROR,
4 | ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS
5 | } from '../errors';
6 |
7 | export function handleAxiosError(error) {
8 | if (error.request) {
9 | // Axios errors.
10 | if (error.response) {
11 | error = new ExtendedError(ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS, error.response);
12 | } else {
13 | error = new ExtendedError(ERROR_HTTP_NETWORK_ERROR);
14 | }
15 | }
16 |
17 | return error;
18 | }
19 |
--------------------------------------------------------------------------------
/identifier/src/app.css:
--------------------------------------------------------------------------------
1 | /* additional css on top of kpop */
2 |
--------------------------------------------------------------------------------
/identifier/src/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Loadable from 'react-loadable';
4 | import { Provider } from 'react-redux';
5 |
6 | import { MuiThemeProvider } from '@material-ui/core/styles';
7 |
8 | import { defaultTheme as theme } from 'kpop/es/theme';
9 | import IntlContainer from 'kpop/es/IntlContainer';
10 | import Loading from 'kpop/es/Loading';
11 | import { unregister } from 'kpop/es/serviceWorker';
12 |
13 | import store from './store';
14 | import translations from './locales';
15 |
16 | const onLocaleChanged = async locale => {
17 | console.info('locale', locale); // eslint-disable-line no-console
18 | };
19 |
20 | // NOTE(longsleep): Load async with loader, this enables code splitting via Webpack.
21 | const LoadableApp = Loadable({
22 | loader: () => import(/* webpackChunkName: "identifier-main" */ './Main'),
23 | loading: Loading,
24 | timeout: 20000
25 | });
26 |
27 | ReactDOM.render(
28 |
29 |
30 |
31 |
32 |
33 |
34 | ,
35 | document.getElementById('root')
36 | );
37 |
38 | unregister();
39 |
--------------------------------------------------------------------------------
/identifier/src/components/ClientDisplayName.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ClientDisplayName = ({ client, ...rest }) => (
5 | {client.display_name ? client.display_name : client.id}
6 | );
7 |
8 | ClientDisplayName.propTypes = {
9 | client: PropTypes.object.isRequired
10 | };
11 |
12 | export default ClientDisplayName;
13 |
--------------------------------------------------------------------------------
/identifier/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | import { FormattedMessage } from 'react-intl';
6 |
7 | import { withStyles } from '@material-ui/core/styles';
8 | import LinearProgress from '@material-ui/core/LinearProgress';
9 | import Grid from '@material-ui/core/Grid';
10 | import Typography from '@material-ui/core/Typography';
11 | import Button from '@material-ui/core/Button';
12 | import renderIf from 'render-if';
13 |
14 | import { retryHello } from '../actions/common';
15 | import { ErrorMessage } from '../errors';
16 |
17 | const styles = theme => ({
18 | root: {
19 | flexGrow: 1,
20 | position: 'absolute',
21 | top: 0,
22 | bottom: 0,
23 | left: 0,
24 | right: 0
25 | },
26 | progress: {
27 | height: '4px',
28 | width: '100px'
29 | },
30 | button: {
31 | marginTop: theme.spacing(5)
32 | }
33 | });
34 |
35 | class Loading extends React.PureComponent {
36 | render() {
37 | const { classes, error } = this.props;
38 |
39 | return (
40 |
41 |
42 | {renderIf(error === null)(() => (
43 |
44 | ))}
45 | {renderIf(error !== null)(() => (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
63 |
64 | ))}
65 |
66 |
67 | );
68 | }
69 |
70 | retry(event) {
71 | event.preventDefault();
72 |
73 | this.props.dispatch(retryHello());
74 | }
75 | }
76 |
77 | Loading.propTypes = {
78 | classes: PropTypes.object.isRequired,
79 |
80 | error: PropTypes.object,
81 |
82 | dispatch: PropTypes.func.isRequired
83 | };
84 |
85 | const mapStateToProps = (state) => {
86 | const { error } = state.common;
87 |
88 | return {
89 | error
90 | };
91 | };
92 |
93 | export default connect(mapStateToProps)(withStyles(styles)(Loading));
94 |
--------------------------------------------------------------------------------
/identifier/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Route } from 'react-router-dom';
4 |
5 | import RedirectWithQuery from './RedirectWithQuery';
6 |
7 | const PrivateRoute = ({ component: Target, hello, ...rest }) => (
8 | (
9 | hello ? (
10 |
11 | ) : (
12 |
13 | )
14 | )}/>
15 | );
16 |
17 | PrivateRoute.propTypes = {
18 | component: PropTypes.func.isRequired,
19 | hello: PropTypes.object
20 | };
21 |
22 | export default PrivateRoute;
23 |
--------------------------------------------------------------------------------
/identifier/src/components/RedirectWithQuery.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withRouter } from 'react-router';
4 | import { Redirect } from 'react-router-dom';
5 |
6 | const RedirectWithQuery = ({target, location, ...rest}) => {
7 | const to = {
8 | pathname: target,
9 | search: location.search,
10 | hash: location.hash
11 | };
12 |
13 | return (
14 |
15 | );
16 | };
17 |
18 | RedirectWithQuery.propTypes = {
19 | target: PropTypes.string.isRequired,
20 | location: PropTypes.object.isRequired
21 | };
22 |
23 | export default withRouter(RedirectWithQuery);
24 |
--------------------------------------------------------------------------------
/identifier/src/components/ResponsiveDialog.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Dialog from '@material-ui/core/Dialog';
5 | import withMobileDialog from '@material-ui/core/withMobileDialog';
6 |
7 | const ResponsiveDialog = (props) => {
8 | return ;
9 | };
10 |
11 | ResponsiveDialog.propTypes = {
12 | fullScreen: PropTypes.bool.isRequired
13 | };
14 |
15 | export default withMobileDialog()(ResponsiveDialog);
16 |
--------------------------------------------------------------------------------
/identifier/src/components/ResponsiveScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import { withStyles } from '@material-ui/core/styles';
6 | import Grid from '@material-ui/core/Grid';
7 | import DialogContent from '@material-ui/core/DialogContent';
8 | import DialogActions from '@material-ui/core/DialogActions';
9 | import LocaleSelect from 'kpop/es/IntlContainer/LocaleSelect';
10 |
11 | import ResponsiveDialog from './ResponsiveDialog';
12 | import KopanoLogo from '../images/kopano-logo.svg';
13 | import Loading from './Loading';
14 |
15 | const styles = theme => ({
16 | root: {
17 | display: 'flex',
18 | flex: 1
19 | },
20 | content: {
21 | paddingTop: 24,
22 | paddingBottom: 12,
23 | minHeight: 500,
24 | paddingLeft: theme.spacing(2),
25 | paddingRight: theme.spacing(2),
26 | position: 'relative'
27 | },
28 | logo: {
29 | height: 18,
30 | marginBottom: theme.spacing(2)
31 | },
32 | actions: {
33 | marginTop: -40,
34 | justifyContent: 'flex-start',
35 | paddingLeft: theme.spacing(3),
36 | paddingRight: theme.spacing(3)
37 | }
38 | });
39 |
40 | const ResponsiveScreen = (props) => {
41 | const {
42 | classes,
43 | withoutLogo,
44 | withoutPadding,
45 | loading,
46 | children,
47 | className,
48 | DialogProps,
49 | PaperProps,
50 | ...other
51 | } = props;
52 |
53 | const logo = withoutLogo ? null :
54 |
;
55 |
56 | const content = loading ? : (withoutPadding ? children : {children});
57 |
58 | return (
59 |
61 |
66 |
67 | {logo}
68 | {content}
69 |
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | ResponsiveScreen.defaultProps = {
77 | withoutLogo: false,
78 | withoutPadding: false,
79 | loading: false
80 | };
81 |
82 | ResponsiveScreen.propTypes = {
83 | classes: PropTypes.object.isRequired,
84 | withoutLogo: PropTypes.bool,
85 | withoutPadding: PropTypes.bool,
86 | loading: PropTypes.bool,
87 | children: PropTypes.node.isRequired,
88 | className: PropTypes.string,
89 | PaperProps: PropTypes.object,
90 | DialogProps: PropTypes.object
91 | };
92 |
93 | export default withStyles(styles)(ResponsiveScreen);
94 |
--------------------------------------------------------------------------------
/identifier/src/components/ScopesList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import List from '@material-ui/core/List';
3 | import ListItem from '@material-ui/core/ListItem';
4 | import ListItemText from '@material-ui/core/ListItemText';
5 | import { withStyles } from '@material-ui/core/styles';
6 | import PropTypes from 'prop-types';
7 | import Checkbox from '@material-ui/core/Checkbox';
8 |
9 | import { injectIntl, intlShape, defineMessages, FormattedMessage } from 'react-intl';
10 |
11 | const styles = () => ({
12 | row: {
13 | paddingTop: 0,
14 | paddingBottom: 0
15 | }
16 | });
17 |
18 | const scopeIDTranslations = defineMessages({
19 | 'scope_alias_basic': {
20 | id: 'konnect.scopeDescription.aliasBasic',
21 | defaultMessage: 'Access your basic account information'
22 | },
23 | 'scope_offline_access': {
24 | id: 'konnect.scopeDescription.offlineAccess',
25 | defaultMessage: 'Keep the allowed access persistently and forever'
26 | }
27 | });
28 |
29 | const ScopesList = ({scopes, meta, classes, intl, ...rest}) => {
30 | const { mapping, definitions } = meta;
31 |
32 | const rows = [];
33 | const known = {};
34 |
35 | // TODO(longsleep): Sort scopes according to priority.
36 | for (let scope in scopes) {
37 | if (!scopes[scope]) {
38 | continue;
39 | }
40 | let id = mapping[scope];
41 | if (id) {
42 | if (known[id]) {
43 | continue;
44 | }
45 | known[id] = true;
46 | } else {
47 | id = scope;
48 | }
49 | let definition = definitions[id];
50 | let label ;
51 | if (definition) {
52 | if (definition.id) {
53 | const translation = scopeIDTranslations[definition.id];
54 | if (translation) {
55 | label = intl.formatMessage(translation);
56 | }
57 | }
58 | if (!label) {
59 | label = definition.description;
60 | }
61 | }
62 | if (!label) {
63 | label = ;
68 | }
69 |
70 | rows.push(
71 |
81 |
82 |
83 | );
84 | }
85 |
86 | return (
87 |
88 | {rows}
89 |
90 | );
91 | };
92 |
93 | ScopesList.propTypes = {
94 | classes: PropTypes.object.isRequired,
95 | intl: intlShape.isRequired,
96 |
97 | scopes: PropTypes.object.isRequired,
98 | meta: PropTypes.object.isRequired
99 | };
100 |
101 | export default withStyles(styles)(injectIntl(ScopesList));
102 |
--------------------------------------------------------------------------------
/identifier/src/containers/Goodbye/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './Goodbyescreen';
2 |
--------------------------------------------------------------------------------
/identifier/src/containers/Login/Loginscreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | import { Route, Switch } from 'react-router-dom';
6 |
7 | import { withStyles } from '@material-ui/core/styles';
8 |
9 | import ResponsiveScreen from '../../components/ResponsiveScreen';
10 | import RedirectWithQuery from '../../components/RedirectWithQuery';
11 | import { executeHello } from '../../actions/common';
12 |
13 | import Login from './Login';
14 | import Chooseaccount from './Chooseaccount';
15 | import Consent from './Consent';
16 |
17 | const styles = () => ({
18 | });
19 |
20 | class Loginscreen extends React.PureComponent {
21 | componentDidMount() {
22 | this.props.dispatch(executeHello());
23 | }
24 |
25 | render() {
26 | const { hello } = this.props;
27 |
28 | const loading = hello === null;
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | Loginscreen.propTypes = {
43 | classes: PropTypes.object.isRequired,
44 |
45 | hello: PropTypes.object,
46 |
47 | dispatch: PropTypes.func.isRequired
48 | };
49 |
50 | const mapStateToProps = (state) => {
51 | const { hello } = state.common;
52 |
53 | return {
54 | hello
55 | };
56 | };
57 |
58 | export default connect(mapStateToProps)(withStyles(styles)(Loginscreen));
59 |
--------------------------------------------------------------------------------
/identifier/src/containers/Login/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './Loginscreen';
2 |
--------------------------------------------------------------------------------
/identifier/src/containers/Welcome/Welcomescreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | import { FormattedMessage } from 'react-intl';
6 |
7 | import { withStyles } from '@material-ui/core/styles';
8 | import Button from '@material-ui/core/Button';
9 | import Typography from '@material-ui/core/Typography';
10 | import DialogActions from '@material-ui/core/DialogActions';
11 |
12 | import ResponsiveScreen from '../../components/ResponsiveScreen';
13 | import { executeLogoff } from '../../actions/common';
14 |
15 | const styles = theme => ({
16 | button: {
17 | margin: theme.spacing(1),
18 | minWidth: 100
19 | },
20 | subHeader: {
21 | marginBottom: theme.spacing(5)
22 | }
23 | });
24 |
25 | class Welcomescreen extends React.PureComponent {
26 | render() {
27 | const { classes, hello } = this.props;
28 |
29 | const loading = hello === null;
30 | return (
31 |
32 |
33 |
37 |
38 |
39 |
40 | {hello.username}
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
57 |
58 |
59 | );
60 | }
61 |
62 | logoff(event) {
63 | event.preventDefault();
64 |
65 | this.props.dispatch(executeLogoff()).then((response) => {
66 | const { history } = this.props;
67 |
68 | if (response.success) {
69 | history.push('/identifier');
70 | }
71 | });
72 | }
73 | }
74 |
75 | Welcomescreen.propTypes = {
76 | classes: PropTypes.object.isRequired,
77 |
78 | hello: PropTypes.object,
79 |
80 | dispatch: PropTypes.func.isRequired,
81 | history: PropTypes.object.isRequired
82 | };
83 |
84 | const mapStateToProps = (state) => {
85 | const { hello } = state.common;
86 |
87 | return {
88 | hello
89 | };
90 | };
91 |
92 | export default connect(mapStateToProps)(withStyles(styles)(Welcomescreen));
93 |
--------------------------------------------------------------------------------
/identifier/src/containers/Welcome/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './Welcomescreen';
2 |
--------------------------------------------------------------------------------
/identifier/src/errors/index.js:
--------------------------------------------------------------------------------
1 | import { injectIntl, defineMessages } from 'react-intl';
2 |
3 | export const ERROR_LOGIN_VALIDATE_MISSINGUSERNAME = 'konnect.error.login.validate.missingUsername';
4 | export const ERROR_LOGIN_VALIDATE_MISSINGPASSWORD = 'konnect.error.login.validate.missingPassword';
5 | export const ERROR_LOGIN_FAILED = 'konnect.error.login.failed';
6 | export const ERROR_HTTP_NETWORK_ERROR = 'konnet.error.http.networkError';
7 | export const ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS = 'konnect.error.http.unexpectedResponseStatus';
8 | export const ERROR_HTTP_UNEXPECTED_RESPONSE_STATE = 'konnect.error.http.unexpectedResponseState';
9 |
10 | // Translatable error messages.
11 | const translations = defineMessages({
12 | [ERROR_LOGIN_VALIDATE_MISSINGUSERNAME]: {
13 | id: ERROR_LOGIN_VALIDATE_MISSINGUSERNAME,
14 | defaultMessage: 'Enter an username'
15 | },
16 | [ERROR_LOGIN_VALIDATE_MISSINGPASSWORD]: {
17 | id: ERROR_LOGIN_VALIDATE_MISSINGPASSWORD,
18 | defaultMessage: 'Enter a password'
19 | },
20 | [ERROR_LOGIN_FAILED]: {
21 | id: ERROR_LOGIN_FAILED,
22 | defaultMessage: 'Logon failed. Please verify your credentials and try again.'
23 | },
24 | [ERROR_HTTP_NETWORK_ERROR]: {
25 | id: ERROR_HTTP_NETWORK_ERROR,
26 | defaultMessage: 'Network error. Please check your connection and try again.'
27 | },
28 | [ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS]: {
29 | id: ERROR_HTTP_UNEXPECTED_RESPONSE_STATUS,
30 | defaultMessage: 'Unexpected HTTP response: {status}. Please check your connection and try again.'
31 | },
32 | [ERROR_HTTP_UNEXPECTED_RESPONSE_STATE]: {
33 | id: ERROR_HTTP_UNEXPECTED_RESPONSE_STATE,
34 | defaultMessage: 'Unexpected response state: {state}'
35 | }
36 | });
37 |
38 | // Error with values.
39 | export class ExtendedError extends Error {
40 | values = undefined;
41 |
42 | constructor(message, values) {
43 | super(message);
44 | if (Error.captureStackTrace !== undefined) {
45 | Error.captureStackTrace(this, ExtendedError);
46 | }
47 | this.values = values;
48 | }
49 | }
50 |
51 | // Component to translate error text with values.
52 | function ErrorMessageComponent(props) {
53 | const { error, intl } = props;
54 |
55 | if (!error) {
56 | return null;
57 | }
58 |
59 | const id = error.id ? error.id : error.message;
60 | const messageDescriptor = Object.assign({}, {
61 | id,
62 | defaultMessage: error.id ? error.message : undefined
63 | }, translations[id]);
64 |
65 | return intl.formatMessage(messageDescriptor, error.values);
66 | }
67 |
68 | export const ErrorMessage = injectIntl(ErrorMessageComponent);
69 |
--------------------------------------------------------------------------------
/identifier/src/fancy-background.css:
--------------------------------------------------------------------------------
1 | /* fancy background with two components and fade */
2 |
3 | #bg {
4 | background-color: white;
5 | }
6 |
7 | #bg > div {
8 | position: absolute;
9 | top: 0;
10 | bottom: 0;
11 | left: 0;
12 | right: 0;
13 | background-size: cover;
14 | background-repeat: no-repeat;
15 | background-position: center;
16 | z-index: 0;
17 | }
18 |
19 | #loader {
20 | /* NOTE(longsleep): White here needed because of the background image */
21 | color: white;
22 | text-shadow: #000 0px 0px 1px;
23 | }
24 |
25 | /* NOTE(longsleep): This imports the inline image CSS as generated by make. */
26 | @import url(./images/loginscreen-bg.css);
27 |
28 | #bg-enhanced {
29 | opacity: 0;
30 | transition: opacity 1s;
31 | }
32 |
33 | #bg-enhanced.enhanced {
34 | background-image: url(./images/loginscreen-bg.jpg);
35 | opacity: 1;
36 | }
37 |
38 | #bg-enhanced.enhanced:after {
39 | content: '';
40 | position: absolute;
41 | top: 0;
42 | bottom: 0;
43 | left: 0;
44 | right: 0;
45 | background-image: url(./images/loginscreen-bg-overlay.svg);
46 | background-size: cover;
47 | background-repeat: no-repeat;
48 | background-position: center;
49 | }
50 |
--------------------------------------------------------------------------------
/identifier/src/images/.gitignore:
--------------------------------------------------------------------------------
1 | /loginscreen-bg-thumb.jpg
2 | /loginscreen-bg-thumb.svg
3 | /loginscreen-bg.css
4 | /loginscreen-bg.jpg
5 | /loginscreen-bg-overlay.svg
6 | app-icon.svg
7 | app-icon-*.png
8 |
--------------------------------------------------------------------------------
/identifier/src/images/Makefile:
--------------------------------------------------------------------------------
1 | # Tools
2 |
3 | CONVERT ?= convert
4 | IDENTIFY ?= identify
5 | BASE64 ?= base64
6 | ENVSUBST ?= envsubst
7 | SCOUR ?= scour
8 | INKSCAPE ?= inkscape
9 |
10 | # Variables
11 |
12 | LOGINSCREEN_BG_SRC ?= kopano-augmenting-teamwork-bg-7680.jpg
13 | LOGINSCREEN_BG_OVERLAY_SRC ?= kopano-augmenting-teamwork-overlay.svg
14 |
15 | LOGINSCREEN_BG_WIDTH = $(shell $(IDENTIFY) -format '%w' loginscreen-bg.jpg)
16 | LOGINSCREEN_BG_HEIGHT = $(shell $(IDENTIFY) -format '%h' loginscreen-bg.jpg)
17 | LOGINSCREEN_BG_THUMB_BASE64 = $(shell $(BASE64) -w0 loginscreen-bg-thumb.jpg)
18 | LOGINSCREEN_BG_THUMB_BASE64_SVG = $(shell $(BASE64) -w0 loginscreen-bg-thumb.svg)
19 |
20 | STATIC ?= ../../public/static
21 | ICON ?= kopano-konnect-icon.svg
22 |
23 | # Build
24 |
25 | all: loginscreen-bg.css
26 |
27 | loginscreen-bg.jpg: $(LOGINSCREEN_BG_SRC)
28 | $(CONVERT) -geometry x1080 -strip -interlace Plane -gaussian-blur 0.05 -define jpeg:dct-method=float -quality 75% $< $@
29 |
30 | loginscreen-bg-thumb.jpg: loginscreen-bg.jpg
31 | $(CONVERT) -geometry x40 -strip -define jpeg:dct-method=float -quality 50% $< $@
32 |
33 | loginscreen-bg-thumb.svg: loginscreen-bg-thumb.svg.in | loginscreen-bg-thumb.jpg loginscreen-bg.jpg
34 | WIDTH=$(LOGINSCREEN_BG_WIDTH) HEIGHT=$(LOGINSCREEN_BG_HEIGHT) \
35 | IMAGE_DATA=$(LOGINSCREEN_BG_THUMB_BASE64) \
36 | $(ENVSUBST) < $< > $@
37 |
38 | loginscreen-bg-overlay.svg: $(LOGINSCREEN_BG_OVERLAY_SRC)
39 | $(SCOUR) --enable-viewboxing --create-groups --shorten-ids --enable-id-stripping \
40 | --enable-comment-stripping --disable-embed-rasters --remove-metadata --strip-xml-prolog -p 9 \
41 | -i $< -o $@
42 |
43 | loginscreen-bg.css: loginscreen-bg.css.in | loginscreen-bg.jpg loginscreen-bg-thumb.svg loginscreen-bg-overlay.svg
44 | IMAGE_DATA=$(LOGINSCREEN_BG_THUMB_BASE64_SVG) \
45 | $(ENVSUBST) < $< > $@
46 |
47 | .PHONY: icons
48 | icons: $(STATIC)/favicon.ico
49 |
50 | $(STATIC)/favicon.ico: app-icon-rounded-256x256.png
51 | $(CONVERT) -background transparent $< -define icon:auto-resize=16,32,48,64,128,256 $@
52 |
53 | app-icon.svg: $(ICON)
54 | cp -vaf $< $@
55 |
56 | app-icon-whitebox-256x256.png: app-icon.svg
57 | $(INKSCAPE) -z -e $@.tmp -w 204.8 -h 204.8 -b white -y 1.0 $<
58 | $(CONVERT) $@.tmp -background white -gravity center -extent 256x256 $@
59 | @$(RM) $@.tmp
60 |
61 | app-icon-rounded-256x256.png: app-icon-whitebox-256x256.png
62 | $(CONVERT) -size 256x256 xc:none -draw "roundrectangle 2,2,252,252,126,126" $@.tmp.png
63 | $(CONVERT) $< -matte $@.tmp.png -compose DstIn -composite $@
64 | @$(RM) $@.tmp.png
65 |
66 | .PHONY: clean
67 | clean:
68 | $(RM) loginscreen-bg.jpg
69 | $(RM) loginscreen-bg-thumb.jpg
70 | $(RM) loginscreen-bg-thumb.svg
71 | $(RM) loginscreen-bg.css
72 | $(RM) loginscreen-bg-overlay.svg
73 | $(RM) app-icon-*.png || true
74 | $(RM) app-icon.svg || true
75 |
--------------------------------------------------------------------------------
/identifier/src/images/kopano-augmenting-teamwork-bg-7680.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kopano-dev/konnect/52c7ab10ee1f2a39323b3f9db1514e0433138e2a/identifier/src/images/kopano-augmenting-teamwork-bg-7680.jpg
--------------------------------------------------------------------------------
/identifier/src/images/kopano-konnect-icon.svg:
--------------------------------------------------------------------------------
1 | ../../node_modules/kpop/static/icons/kopano-konnect-icon.svg
--------------------------------------------------------------------------------
/identifier/src/images/loginscreen-bg-thumb.svg.in:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/identifier/src/images/loginscreen-bg.css.in:
--------------------------------------------------------------------------------
1 | #bg-thumb {
2 | /* NOTE(longsleep): This is the base64 encoded svg, inlined directly to show
3 | this image as early as possible.
4 | */
5 | background-image: url("data:image/svg+xml;base64,${IMAGE_DATA}");
6 | }
7 |
--------------------------------------------------------------------------------
/identifier/src/index.js:
--------------------------------------------------------------------------------
1 | import 'kpop/static/css/base.css';
2 | import 'kpop/static/css/scrollbar.css';
3 | import 'typeface-roboto';
4 | import './app.css';
5 | import './fancy-background.css';
6 |
7 | import * as kpop from 'kpop/es/version';
8 |
9 | import * as version from './version';
10 |
11 | console.info(`Kopano Identifier build version: ${version.build}`); // eslint-disable-line no-console
12 | console.info(`Kopano Kpop build version: ${kpop.build}`); // eslint-disable-line no-console
13 |
14 | // NOTE(longsleep): Load async, this enables code splitting via Webpack.
15 | import(/* webpackChunkName: "identifier-app" */ './app');
16 |
--------------------------------------------------------------------------------
/identifier/src/locales/index.js:
--------------------------------------------------------------------------------
1 | // NOTE(longsleep): This loads all translation files to be included in the
2 | // app bundle. They are not that large.
3 |
4 | // Please keep imports and exports alphabetically sorted.
5 | import de from './de.json';
6 | import fr from './fr.json';
7 | import hi from './hi.json';
8 | import is from './is.json';
9 | import nb from './nb.json';
10 | import nl from './nl.json';
11 | import ptPT from './pt_PT.json';
12 | import ru from './ru.json';
13 |
14 | function enableLocales(locales, enabled) {
15 | if (process.env.NODE_ENV !== 'production') { // eslint-disable-line no-undef
16 | return locales;
17 | }
18 | return enabled.reduce(function(value, locale) {
19 | value[locale] = locales[locale];
20 | return value;
21 | }, {});
22 | }
23 |
24 | // Locales must follow BCP 47 format (https://tools.ietf.org/html/rfc5646).
25 | const locales = enableLocales({
26 | de,
27 | 'en-GB': {},
28 | 'en-US': {},
29 | fr,
30 | hi,
31 | is,
32 | nb,
33 | nl,
34 | 'pt-PT': ptPT,
35 | ru
36 | }, [
37 | // List of enabled languages in production builds.
38 | 'de',
39 | 'en-GB',
40 | 'en-US',
41 | 'fr',
42 | 'hi',
43 | 'is',
44 | 'nb',
45 | 'nl',
46 | 'pt-PT',
47 | 'ru'
48 | ]);
49 |
50 | export default locales;
51 |
--------------------------------------------------------------------------------
/identifier/src/models/hello.js:
--------------------------------------------------------------------------------
1 | export function newHelloRequest(flow, query) {
2 | const r = {};
3 |
4 | if (query.prompt) {
5 | // TODO(longsleep): Validate prompt values?
6 | r.prompt = query.prompt;
7 | }
8 |
9 | let selectedFlow = flow;
10 | switch (flow) {
11 | case 'oauth':
12 | case 'consent':
13 | case 'oidc':
14 | r.scope = query.scope || '';
15 | r.client_id = query.client_id || ''; // eslint-disable-line camelcase
16 | r.redirect_uri = query.redirect_uri || ''; // eslint-disable-line camelcase
17 | if (query.id_token_hint) {
18 | r.id_token_hint = query.id_token_hint; // eslint-disable-line camelcase
19 | }
20 | if (query.max_age) {
21 | r.max_age = query.max_age; // eslint-disable-line camelcase
22 | }
23 | if (query.claims_scope) {
24 | // Add additional scopes from claims request if given.
25 | r.scope += ' ' + query.claims_scope;
26 | }
27 | break;
28 |
29 | default:
30 | selectedFlow = null;
31 | }
32 |
33 | if (selectedFlow) {
34 | r.flow = selectedFlow;
35 | }
36 |
37 | return r;
38 | }
39 |
--------------------------------------------------------------------------------
/identifier/src/reducers/common.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_ERROR,
3 | RESET_HELLO,
4 | RECEIVE_HELLO,
5 | SERVICE_WORKER_NEW_CONTENT
6 | } from '../actions/types';
7 | import queryString from 'query-string';
8 |
9 | const query = queryString.parse(document.location.search);
10 | const flow = query.flow || '';
11 | delete query.flow;
12 |
13 | const defaultPathPrefix = (() => {
14 | const root = document.getElementById('root');
15 | let pathPrefix = root ? root.getAttribute('data-path-prefix') : null;
16 | if (!pathPrefix || pathPrefix === '__PATH_PREFIX__') {
17 | // Not replaced, probably we are running in debug mode or whatever. Use sane default.
18 | pathPrefix = '/signin/v1';
19 | }
20 | return pathPrefix;
21 | })();
22 |
23 | const defaultState = {
24 | hello: null,
25 | error: null,
26 | flow: flow,
27 | query: query,
28 | updateAvailable: false,
29 | pathPrefix: defaultPathPrefix
30 | };
31 |
32 | function commonReducer(state = defaultState, action) {
33 | switch (action.type) {
34 | case RECEIVE_ERROR:
35 | return Object.assign({}, state, {
36 | error: action.error
37 | });
38 |
39 | case RESET_HELLO:
40 | return Object.assign({}, state, {
41 | hello: null
42 | });
43 |
44 | case RECEIVE_HELLO:
45 | return Object.assign({}, state, {
46 | hello: {
47 | state: action.state,
48 | username: action.username,
49 | displayName: action.displayName,
50 | details: action.hello
51 | }
52 | });
53 |
54 | case SERVICE_WORKER_NEW_CONTENT:
55 | return Object.assign({}, state, {
56 | updateAvailable: true
57 | });
58 |
59 | default:
60 | return state;
61 | }
62 | }
63 |
64 | export default commonReducer;
65 |
--------------------------------------------------------------------------------
/identifier/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import commonReducer from './common';
4 | import loginReducer from './login';
5 |
6 | const rootReducer = combineReducers({
7 | common: commonReducer,
8 | login: loginReducer
9 | });
10 |
11 | export default rootReducer;
12 |
--------------------------------------------------------------------------------
/identifier/src/reducers/login.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_VALIDATE_LOGON,
3 | REQUEST_LOGON,
4 | RECEIVE_LOGON,
5 | RECEIVE_LOGOFF,
6 | REQUEST_CONSENT_ALLOW,
7 | REQUEST_CONSENT_CANCEL,
8 | RECEIVE_CONSENT,
9 | UPDATE_INPUT
10 | } from '../actions/types';
11 |
12 | function loginReducer(state = {
13 | loading: '',
14 | username: '',
15 | password: '',
16 | errors: {}
17 | }, action) {
18 | switch (action.type) {
19 | case RECEIVE_VALIDATE_LOGON:
20 | return Object.assign({}, state, {
21 | errors: action.errors,
22 | loading: ''
23 | });
24 |
25 | case REQUEST_CONSENT_ALLOW:
26 | case REQUEST_CONSENT_CANCEL:
27 | case REQUEST_LOGON:
28 | return Object.assign({}, state, {
29 | loading: action.type,
30 | errors: {}
31 | });
32 |
33 | case RECEIVE_CONSENT:
34 | case RECEIVE_LOGON:
35 | if (!action.success) {
36 | return Object.assign({}, state, {
37 | errors: action.errors ? action.errors : {},
38 | loading: ''
39 | });
40 | }
41 | return state;
42 |
43 | case RECEIVE_LOGOFF:
44 | return Object.assign({}, state, {
45 | username: '',
46 | password: ''
47 | });
48 |
49 | case UPDATE_INPUT:
50 | delete state.errors[action.name];
51 | return Object.assign({}, state, {
52 | [action.name]: action.value
53 | });
54 |
55 | default:
56 | return state;
57 | }
58 | }
59 |
60 | export default loginReducer;
61 |
--------------------------------------------------------------------------------
/identifier/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunkMiddleware from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | import rootReducer from './reducers';
6 |
7 | const middlewares = [
8 | thunkMiddleware
9 | ];
10 |
11 | if (process.env.NODE_ENV === 'development') { // eslint-disable-line no-undef
12 | middlewares.push(createLogger()); // must be last middleware in the chain.
13 | }
14 |
15 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
16 |
17 | const store = createStore(
18 | rootReducer,
19 | composeEnhancers(applyMiddleware(
20 | ...middlewares,
21 | ))
22 | );
23 |
24 | export default store;
25 |
--------------------------------------------------------------------------------
/identifier/src/utils.js:
--------------------------------------------------------------------------------
1 | export function withClientRequestState(obj) {
2 | obj.state = Math.random().toString(36).substring(7);
3 |
4 | return obj;
5 | }
6 |
7 | export function dirname(s) {
8 | return s.replace(/\\/g,'/').replace(/\/[^/]*$/, '');
9 | }
10 |
11 | export function propertyFromStylesheet(selector, attribute, asURL=false) {
12 | let value;
13 | let sheetHref;
14 |
15 | Array.prototype.some.call(document.styleSheets, function(sheet) {
16 | try {
17 | return Array.prototype.some.call(sheet.cssRules, function(rule) {
18 | sheetHref = sheet.href;
19 | if (selector === rule.selectorText) {
20 | return Array.prototype.some.call(rule.style, function(style) {
21 | if (attribute === style) {
22 | value = rule.style.getPropertyValue(attribute);
23 | return true;
24 | }
25 |
26 | return false;
27 | });
28 | }
29 |
30 | return false;
31 | });
32 | } catch(e) {
33 | // Ignore sheels which caused errors. This for example can happen if an
34 | // extension injected styles from an other origin.
35 | return false;
36 | }
37 | });
38 |
39 | if (value && asURL) {
40 | // This removes url() shit if there.
41 | value = value.match(/(?:\(['|"]?)(.*?)(?:['|"]?\))/)[1];
42 | if (!value) {
43 | return null;
44 | }
45 | if (sheetHref) {
46 | // URLs in CSS are relative to the CSS - so lets add stuff.
47 | const baseHref = dirname(sheetHref);
48 | value = baseHref + '/' + value;
49 | }
50 | }
51 |
52 | return value;
53 | }
54 |
55 | export function enhanceBodyBackground() {
56 | const bg = propertyFromStylesheet('#bg-enhanced.enhanced', 'background-image', true);
57 | const overlay = propertyFromStylesheet('#bg-enhanced.enhanced::after', 'background-image', true);
58 |
59 | const promises = [];
60 | if (bg) {
61 | promises.push(new Promise(resolve => {
62 | const img = new Image();
63 | img.onload = () => {
64 | resolve();
65 | };
66 | // Set image source to whatever the url from css holds.
67 | img.src = bg;
68 | }));
69 | }
70 | if (overlay) {
71 | promises.push(new Promise(resolve => {
72 | const img = new Image();
73 | img.onload = () => {
74 | resolve();
75 | };
76 | // Set image source to whatever the url from css holds.
77 | img.src = overlay;
78 | }));
79 | }
80 | Promise.all(promises).then(() => {
81 | window.document.getElementById('bg-enhanced').className += ' enhanced';
82 | });
83 | }
84 |
--------------------------------------------------------------------------------
/identifier/src/version.js:
--------------------------------------------------------------------------------
1 | /*global process: true*/
2 |
3 | const build = process.env.REACT_APP_KOPANO_BUILD || '0.0.0-no-proper-build';
4 |
5 | export {
6 | build
7 | };
8 |
--------------------------------------------------------------------------------
/identifier/trampolin.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | import (
21 | "html/template"
22 | "net/http"
23 | "net/url"
24 |
25 | "stash.kopano.io/kc/konnect/version"
26 | )
27 |
28 | var trampolinTemplate = template.Must(template.New("trampolin").Parse(`
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | `))
39 |
40 | var trampolinScript = []byte(`(function(window) {
41 | window.location.replace(document.body.getAttribute('trampolin'));
42 | }(window));
43 | `)
44 |
45 | var trampolinVersion = url.QueryEscape(version.Version)
46 |
47 | type trampolinData struct {
48 | URI string
49 | Version string
50 | }
51 |
52 | func (i *Identifier) writeTrampolinHTML(rw http.ResponseWriter, req *http.Request, uri *url.URL) {
53 | data := &trampolinData{
54 | URI: uri.String(),
55 | Version: trampolinVersion,
56 | }
57 |
58 | rw.Header().Set("Content-Type", "text/html")
59 | rw.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
60 | rw.Header().Set("Pragma", "no-cache")
61 | rw.Header().Set("Expires", "0")
62 | err := trampolinTemplate.Execute(rw, data)
63 | if err != nil {
64 | i.logger.WithError(err).Errorln("failed to write trampolin")
65 | }
66 | }
67 |
68 | func (i *Identifier) writeTrampolinScript(rw http.ResponseWriter, req *http.Request) {
69 | rw.Header().Set("Content-Type", "application/javascript")
70 | rw.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
71 |
72 | rw.Write(trampolinScript)
73 | }
74 |
--------------------------------------------------------------------------------
/identifier/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identifier
19 |
20 | import (
21 | "net/http"
22 | "time"
23 | )
24 |
25 | var (
26 | farPastExpiryTime = time.Unix(0, 0)
27 | farPastExpiryTimeHTTPHeaderString = farPastExpiryTime.UTC().Format(http.TimeFormat)
28 | )
29 |
30 | func addCommonResponseHeaders(header http.Header) {
31 | header.Set("X-Frame-Options", "DENY")
32 | header.Set("X-XSS-Protection", "1; mode=block")
33 | header.Set("X-Content-Type-Options", "nosniff")
34 | header.Set("Referrer-Policy", "origin")
35 | }
36 |
37 | func addNoCacheResponseHeaders(header http.Header) {
38 | header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
39 | header.Set("Pragma", "no-cache")
40 | header.Set("Expires", farPastExpiryTimeHTTPHeaderString)
41 | }
42 |
--------------------------------------------------------------------------------
/identity/auth.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "time"
22 |
23 | "github.com/dgrijalva/jwt-go"
24 |
25 | "stash.kopano.io/kc/konnect/oidc/payload"
26 | )
27 |
28 | // AuthRecord is an interface which provides identity auth information with scopes and claims..
29 | type AuthRecord interface {
30 | Manager() Manager
31 | Subject() string
32 | AuthorizedScopes() map[string]bool
33 | AuthorizeScopes(map[string]bool)
34 | AuthorizedClaims() *payload.ClaimsRequest
35 | AuthorizeClaims(*payload.ClaimsRequest)
36 | Claims(...string) []jwt.Claims
37 |
38 | User() PublicUser
39 | SetUser(PublicUser)
40 |
41 | LoggedOn() (bool, time.Time)
42 | SetAuthTime(time.Time)
43 | }
44 |
--------------------------------------------------------------------------------
/identity/authorities/models.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package authorities
19 |
20 | import (
21 | "context"
22 | "net/http"
23 | "net/url"
24 |
25 | "gopkg.in/square/go-jose.v2"
26 | )
27 |
28 | // Supported Authority kind string values.
29 | const (
30 | AuthorityTypeOIDC = "oidc"
31 | AuthorityTypeSAML2 = "saml2"
32 | )
33 |
34 | type authorityRegistrationData struct {
35 | ID string `yaml:"id"`
36 | Name string `yaml:"name"`
37 | AuthorityType string `yaml:"authority_type"`
38 |
39 | Iss string `yaml:"iss"`
40 |
41 | ClientID string `yaml:"client_id"`
42 | ClientSecret string `yaml:"client_secret"`
43 |
44 | EntityID string `yaml:"entity_id"`
45 |
46 | Trusted bool `yaml:"trusted"`
47 | Insecure bool `yaml:"insecure"`
48 | Default bool `yaml:"default"`
49 | Discover *bool `yaml:"discover"`
50 |
51 | Scopes []string `yaml:"scopes"`
52 | ResponseType string `yaml:"response_type"`
53 | CodeChallengeMethod string `yaml:"code_challenge_method"`
54 |
55 | RawMetadataEndpoint string `yaml:"metadata_endpoint"`
56 | RawAuthorizationEndpoint string `yaml:"authorization_endpoint"`
57 |
58 | JWKS *jose.JSONWebKeySet `yaml:"jwks"`
59 |
60 | IdentityClaimName string `yaml:"identity_claim_name"`
61 |
62 | IdentityAliases map[string]string `yaml:"identity_aliases,flow"`
63 | IdentityAliasRequired bool `yaml:"identity_alias_required"`
64 |
65 | EndSessionEnabled bool `yaml:"end_session_enabled"`
66 | }
67 |
68 | type authorityRegistryData struct {
69 | Authorities []*authorityRegistrationData `yaml:"authorities,flow"`
70 | }
71 |
72 | // AuthorityRegistration defines an authority with its properties.
73 | type AuthorityRegistration interface {
74 | ID() string
75 | Name() string
76 | AuthorityType() string
77 |
78 | Authority() *Details
79 | Issuer() string
80 |
81 | Validate() error
82 |
83 | Initialize(ctx context.Context, registry *Registry) error
84 |
85 | MakeRedirectAuthenticationRequestURL(state string) (*url.URL, map[string]interface{}, error)
86 | MakeRedirectEndSessionRequestURL(ref interface{}, state string) (*url.URL, map[string]interface{}, error)
87 | MakeRedirectEndSessionResponseURL(req interface{}, state string) (*url.URL, map[string]interface{}, error)
88 |
89 | ParseStateResponse(req *http.Request, state string, extra map[string]interface{}) (interface{}, error)
90 |
91 | ValidateIdpEndSessionRequest(req interface{}, state string) (bool, error)
92 | ValidateIdpEndSessionResponse(res interface{}, state string) (bool, error)
93 |
94 | IdentityClaimValue(data interface{}) (string, map[string]interface{}, error)
95 |
96 | Metadata() AuthorityMetadata
97 | }
98 |
99 | type AuthorityMetadata interface {
100 | }
101 |
--------------------------------------------------------------------------------
/identity/authorities/samlext/logoutrequest.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2020 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package samlext
19 |
20 | import (
21 | "bytes"
22 | "compress/flate"
23 | "encoding/base64"
24 | "net/url"
25 |
26 | "github.com/beevik/etree"
27 | "github.com/crewjam/saml"
28 | )
29 |
30 | type LogoutRequest struct {
31 | *saml.LogoutRequest
32 | }
33 |
34 | // Redirect returns a URL suitable for using the redirect binding with the response.
35 | func (req *LogoutRequest) Redirect(relayState string) *url.URL {
36 | w := &bytes.Buffer{}
37 | w1 := base64.NewEncoder(base64.StdEncoding, w)
38 | w2, _ := flate.NewWriter(w1, 9)
39 | doc := etree.NewDocument()
40 | doc.SetRoot(req.Element())
41 | if _, err := doc.WriteTo(w2); err != nil {
42 | panic(err)
43 | }
44 | w2.Close()
45 | w1.Close()
46 |
47 | rv, _ := url.Parse(req.Destination)
48 |
49 | query := rv.Query()
50 | query.Set("SAMLRequest", w.String())
51 | if relayState != "" {
52 | query.Set("RelayState", relayState)
53 | }
54 | rv.RawQuery = query.Encode()
55 |
56 | return rv
57 | }
58 |
--------------------------------------------------------------------------------
/identity/authorities/samlext/logoutresponse.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2020 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package samlext
19 |
20 | import (
21 | "bytes"
22 | "compress/flate"
23 | "encoding/base64"
24 | "encoding/xml"
25 | "fmt"
26 | "net/url"
27 |
28 | "github.com/crewjam/saml"
29 | "stash.kopano.io/kgol/rndm"
30 | )
31 |
32 | func MakeLogoutResponse(sp *saml.ServiceProvider, req *saml.LogoutRequest, status *saml.Status, binding string) (*LogoutResponse, error) {
33 |
34 | res := &LogoutResponse{&saml.LogoutResponse{
35 | ID: fmt.Sprintf("id-%x", rndm.GenerateRandomBytes(20)),
36 | InResponseTo: req.ID,
37 |
38 | Version: "2.0",
39 | IssueInstant: saml.TimeNow(),
40 | Destination: sp.GetSLOBindingLocation(binding),
41 |
42 | Issuer: &saml.Issuer{
43 | Format: "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
44 | Value: firstSet(sp.EntityID, sp.MetadataURL.String()),
45 | },
46 | }}
47 |
48 | if status != nil {
49 | res.LogoutResponse.Status = *status
50 | }
51 |
52 | return res, nil
53 | }
54 |
55 | func firstSet(a, b string) string {
56 | if a == "" {
57 | return b
58 | }
59 | return a
60 | }
61 |
62 | type LogoutResponse struct {
63 | *saml.LogoutResponse
64 | }
65 |
66 | // Redirect returns a URL suitable for using the redirect binding with the response.
67 | func (res *LogoutResponse) Redirect(relayState string) *url.URL {
68 | w := &bytes.Buffer{}
69 | w1 := base64.NewEncoder(base64.StdEncoding, w)
70 | w2, _ := flate.NewWriter(w1, 9)
71 | e := xml.NewEncoder(w2)
72 | if err := e.Encode(res); err != nil {
73 | panic(err)
74 | }
75 | w2.Close()
76 | w1.Close()
77 |
78 | rv, _ := url.Parse(res.Destination)
79 |
80 | query := rv.Query()
81 | query.Set("SAMLResponse", w.String())
82 | if relayState != "" {
83 | query.Set("RelayState", relayState)
84 | }
85 | rv.RawQuery = query.Encode()
86 |
87 | return rv
88 | }
89 |
--------------------------------------------------------------------------------
/identity/clients/claims.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package clients
19 |
20 | import (
21 | "github.com/dgrijalva/jwt-go"
22 | )
23 |
24 | // RegistrationClaims are claims used to with dynamic clients.
25 | type RegistrationClaims struct {
26 | jwt.StandardClaims
27 |
28 | *ClientRegistration
29 | }
30 |
31 | // Valid implements the jwt claims interface.
32 | func (crc RegistrationClaims) Valid() error {
33 | return crc.StandardClaims.Valid()
34 | }
35 |
--------------------------------------------------------------------------------
/identity/clients/clients.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package clients
19 |
20 | import (
21 | "crypto"
22 | "net/url"
23 | )
24 |
25 | // Details hold detail information about clients identified by ID.
26 | type Details struct {
27 | ID string `json:"id"`
28 | DisplayName string `json:"display_name"`
29 | RedirectURI string `json:"redirect_uri"`
30 | Trusted bool `json:"trusted"`
31 |
32 | Registration *ClientRegistration `json:"-"`
33 | }
34 |
35 | // A Secured is a client records public key identified by ID.
36 | type Secured struct {
37 | ID string
38 | DisplayName string
39 | ApplicationType string
40 |
41 | Kid string
42 | PublicKey crypto.PublicKey
43 |
44 | TrustedScopes []string
45 |
46 | Registration *ClientRegistration
47 | }
48 |
49 | // IsLocalNativeHTTPURI returns true if the provided URI qualifies to be used
50 | // as http redirect URI for a native client.
51 | func IsLocalNativeHTTPURI(uri *url.URL) bool {
52 | if uri.Scheme != "http" {
53 | return false
54 | }
55 | return IsLocalNativeHostURI(uri)
56 | }
57 |
58 | // IsLocalNativeHostURI returns true if the provided URI hostname is considered
59 | // as localhost for a native client.
60 | func IsLocalNativeHostURI(uri *url.URL) bool {
61 | hostname := uri.Hostname()
62 | return hostname == "localhost" || hostname == "127.0.0.1" || hostname == "::1"
63 | }
64 |
--------------------------------------------------------------------------------
/identity/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "net/url"
22 |
23 | "github.com/sirupsen/logrus"
24 | )
25 |
26 | // Config defines a IdentityManager's configuration settings.
27 | type Config struct {
28 | SignInFormURI *url.URL
29 | SignedOutURI *url.URL
30 |
31 | ScopesSupported []string
32 |
33 | Logger logrus.FieldLogger
34 | }
35 |
--------------------------------------------------------------------------------
/identity/context.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "context"
22 | )
23 |
24 | // key is an unexported type for keys defined in this package.
25 | // This prevents collisions with keys defined in other packages.
26 | type key int
27 |
28 | // authRecordKey is the key for identity.AuthRecord in Contexts. It is
29 | // unexported; clients use identity.NewContext and identity.FromContext
30 | // instead of using this key directly.
31 | var authRecordKey key
32 |
33 | // NewContext returns a new Context that carries value auth.
34 | func NewContext(ctx context.Context, auth AuthRecord) context.Context {
35 | return context.WithValue(ctx, authRecordKey, auth)
36 | }
37 |
38 | // FromContext returns the AuthRecord value stored in ctx, if any.
39 | func FromContext(ctx context.Context) (AuthRecord, bool) {
40 | auth, ok := ctx.Value(authRecordKey).(AuthRecord)
41 | return auth, ok
42 | }
43 |
--------------------------------------------------------------------------------
/identity/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "net/url"
22 | )
23 |
24 | // IsHandledError is an error which tells that the backend has handled
25 | // the request and all further handling should stop
26 | type IsHandledError struct {
27 | }
28 |
29 | // Error implements the error interface.
30 | func (err *IsHandledError) Error() string {
31 | return "is_handled"
32 | }
33 |
34 | // RedirectError is an error which backends can return if a
35 | // redirection is required.
36 | type RedirectError struct {
37 | id string
38 | redirectURI *url.URL
39 | }
40 |
41 | // NewRedirectError creates a new corresponding error with the
42 | // provided id and redirect URL.
43 | func NewRedirectError(id string, redirectURI *url.URL) *RedirectError {
44 | return &RedirectError{
45 | id: id,
46 | redirectURI: redirectURI,
47 | }
48 | }
49 |
50 | // Error implements the error interface.
51 | func (err *RedirectError) Error() string {
52 | return err.id
53 | }
54 |
55 | // RedirectURI returns the redirection URL of the accociated error.
56 | func (err *RedirectError) RedirectURI() *url.URL {
57 | return err.redirectURI
58 | }
59 |
60 | // LoginRequiredError which backends can return to indicate that sign-in is
61 | // required.
62 | type LoginRequiredError struct {
63 | id string
64 | signInURI *url.URL
65 | }
66 |
67 | // NewLoginRequiredError creates a new corresponding error with the provided id.
68 | func NewLoginRequiredError(id string, signInURI *url.URL) *LoginRequiredError {
69 | return &LoginRequiredError{
70 | id: id,
71 | signInURI: signInURI,
72 | }
73 | }
74 |
75 | // Error implements the error interface.
76 | func (err *LoginRequiredError) Error() string {
77 | return err.id
78 | }
79 |
80 | // SignInURI returns the sign-in URL of the accociated error.
81 | func (err *LoginRequiredError) SignInURI() *url.URL {
82 | return err.signInURI
83 | }
84 |
--------------------------------------------------------------------------------
/identity/manager.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "context"
22 | "net/http"
23 |
24 | "github.com/gorilla/mux"
25 |
26 | "stash.kopano.io/kc/konnect/oidc/payload"
27 | )
28 |
29 | // Manager is a interface to define a identity manager.
30 | type Manager interface {
31 | Authenticate(ctx context.Context, rw http.ResponseWriter, req *http.Request, ar *payload.AuthenticationRequest, next Manager) (AuthRecord, error)
32 | Authorize(ctx context.Context, rw http.ResponseWriter, req *http.Request, ar *payload.AuthenticationRequest, auth AuthRecord) (AuthRecord, error)
33 | EndSession(ctx context.Context, rw http.ResponseWriter, req *http.Request, esr *payload.EndSessionRequest) error
34 |
35 | ApproveScopes(ctx context.Context, sub string, audience string, approvedScopesList map[string]bool) (string, error)
36 | ApprovedScopes(ctx context.Context, sub string, audience string, ref string) (map[string]bool, error)
37 |
38 | Fetch(ctx context.Context, userID string, sessionRef *string, scopes map[string]bool, requestedClaimsMaps []*payload.ClaimsRequestMap) (AuthRecord, bool, error)
39 |
40 | Name() string
41 | ScopesSupported(scopes map[string]bool) []string
42 | ClaimsSupported(claims []string) []string
43 |
44 | AddRoutes(ctx context.Context, router *mux.Router)
45 |
46 | OnSetLogon(func(ctx context.Context, rw http.ResponseWriter, user User) error) error
47 | OnUnsetLogon(func(ctx context.Context, rw http.ResponseWriter) error) error
48 | }
49 |
--------------------------------------------------------------------------------
/identity/managers/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package managers
19 |
20 | import (
21 | "encoding/base64"
22 |
23 | "golang.org/x/crypto/blake2b"
24 |
25 | konnectoidc "stash.kopano.io/kc/konnect/oidc"
26 | )
27 |
28 | func setupSupportedScopes(scopes []string, extra []string, override []string) []string {
29 | if len(override) > 0 {
30 | return override
31 | }
32 |
33 | return append(scopes, extra...)
34 | }
35 |
36 | func getPublicSubject(sub []byte, extra []byte) (string, error) {
37 | // Hash the raw subject with a konnect specific salt.
38 | hasher, err := blake2b.New512([]byte(konnectoidc.KonnectIDTokenSubjectSaltV1))
39 | if err != nil {
40 | return "", err
41 | }
42 |
43 | hasher.Write(sub)
44 | hasher.Write([]byte(" "))
45 | hasher.Write(extra)
46 |
47 | // NOTE(longsleep): URL safe encoding for subject is important since many
48 | // third party applications validate this with rather strict patterns.
49 | s := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
50 | return s + "@konnect", nil
51 | }
52 |
--------------------------------------------------------------------------------
/identity/user.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package identity
19 |
20 | import (
21 | "github.com/dgrijalva/jwt-go"
22 | )
23 |
24 | // User defines a most simple user with an id defined as subject.
25 | type User interface {
26 | Subject() string
27 | }
28 |
29 | // UserWithEmail is a User with Email.
30 | type UserWithEmail interface {
31 | User
32 | Email() string
33 | EmailVerified() bool
34 | }
35 |
36 | // UserWithProfile is a User with Name.
37 | type UserWithProfile interface {
38 | User
39 | Name() string
40 | FamilyName() string
41 | GivenName() string
42 | }
43 |
44 | // UserWithID is a User with a locally unique numeric id.
45 | type UserWithID interface {
46 | User
47 | ID() int64
48 | }
49 |
50 | // UserWithUniqueID is a User with a unique string id.
51 | type UserWithUniqueID interface {
52 | User
53 | UniqueID() string
54 | }
55 |
56 | // UserWithUsername is a User with an username different from subject.
57 | type UserWithUsername interface {
58 | User
59 | Username() string
60 | }
61 |
62 | // UserWithClaims is a User with jwt claims.
63 | type UserWithClaims interface {
64 | User
65 | Claims() jwt.MapClaims
66 | }
67 |
68 | // UserWithScopedClaims is a user with jwt claims bound to provided scopes.
69 | type UserWithScopedClaims interface {
70 | User
71 | ScopedClaims(authorizedScopes map[string]bool) jwt.MapClaims
72 | }
73 |
74 | // UserWithSessionRef is a user which supports an underlaying session reference.
75 | type UserWithSessionRef interface {
76 | User
77 | SessionRef() *string
78 | }
79 |
80 | // PublicUser is a user with a public Subject and a raw id.
81 | type PublicUser interface {
82 | Subject() string
83 | Raw() string
84 | }
85 |
--------------------------------------------------------------------------------
/managers/managers.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package managers
19 |
20 | import (
21 | "fmt"
22 | )
23 |
24 | // ServiceUsesManagers is an interface for service which register to managers.
25 | type ServiceUsesManagers interface {
26 | RegisterManagers(mgrs *Managers) error
27 | }
28 |
29 | // Managers is a registry for named managers.
30 | type Managers struct {
31 | registry map[string]interface{}
32 | }
33 |
34 | // New creates a new Managers.
35 | func New() *Managers {
36 | return &Managers{
37 | registry: make(map[string]interface{}),
38 | }
39 | }
40 |
41 | // Set adds the provided manager with the provided name to the accociated
42 | // Managers.
43 | func (m *Managers) Set(name string, manager interface{}) {
44 | m.registry[name] = manager
45 | }
46 |
47 | // Get returns the manager identified by the given name from the accociated
48 | // managers.
49 | func (m *Managers) Get(name string) (interface{}, bool) {
50 | manager, ok := m.registry[name]
51 |
52 | return manager, ok
53 | }
54 |
55 | // Must returns the manager indentified by the given name or panics.
56 | func (m *Managers) Must(name string) interface{} {
57 | manager, ok := m.Get(name)
58 | if !ok {
59 | panic(fmt.Errorf("manager %s not found", name))
60 | }
61 |
62 | return manager
63 | }
64 |
65 | // Apply registers the accociated manager's registered managers.
66 | func (m *Managers) Apply() error {
67 | for _, manager := range m.registry {
68 | if service, ok := manager.(ServiceUsesManagers); ok {
69 | err := service.RegisterManagers(m)
70 | if err != nil {
71 | return err
72 | }
73 | }
74 | }
75 |
76 | return nil
77 | }
78 |
--------------------------------------------------------------------------------
/oidc/claims.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package oidc
19 |
20 | import (
21 | "github.com/dgrijalva/jwt-go"
22 | )
23 |
24 | // IDTokenClaims define the claims found in OIDC ID Tokens.
25 | type IDTokenClaims struct {
26 | jwt.StandardClaims
27 |
28 | Nonce string `json:"nonce,omitempty"`
29 | AuthTime int64 `json:"auth_time,omitempty"`
30 | AccessTokenHash string `json:"at_hash,omitempty"`
31 | CodeHash string `json:"c_hash,omitempty"`
32 |
33 | *ProfileClaims
34 | *EmailClaims
35 |
36 | *SessionClaims
37 | }
38 |
39 | // Valid implements the jwt.Claims interface.
40 | func (c IDTokenClaims) Valid() (err error) {
41 | return c.StandardClaims.Valid()
42 | }
43 |
44 | // ProfileClaims define the claims for the OIDC profile scope.
45 | // https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
46 | type ProfileClaims struct {
47 | Name string `json:"name,omitempty"`
48 | FamilyName string `json:"family_name,omitempty"`
49 | GivenName string `json:"given_name,omitempty"`
50 | PreferredUsername string `json:"preferred_username,omitempty"`
51 | }
52 |
53 | // NewProfileClaims return a new ProfileClaims set from the provided
54 | // jwt.Claims or nil.
55 | func NewProfileClaims(claims jwt.Claims) *ProfileClaims {
56 | if claims == nil {
57 | return nil
58 | }
59 |
60 | return claims.(*ProfileClaims)
61 | }
62 |
63 | // Valid implements the jwt.Claims interface.
64 | func (c ProfileClaims) Valid() error {
65 | return nil
66 | }
67 |
68 | // EmailClaims define the claims for the OIDC email scope.
69 | // https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
70 | type EmailClaims struct {
71 | Email string `json:"email,omitempty"`
72 | EmailVerified bool `json:"email_verified"`
73 | }
74 |
75 | // NewEmailClaims return a new EmailClaims set from the provided
76 | // jwt.Claims or nil.
77 | func NewEmailClaims(claims jwt.Claims) *EmailClaims {
78 | if claims == nil {
79 | return nil
80 | }
81 |
82 | return claims.(*EmailClaims)
83 | }
84 |
85 | // Valid implements the jwt.Claims interface.
86 | func (c EmailClaims) Valid() error {
87 | return nil
88 | }
89 |
90 | // UserInfoClaims define the claims defined by the OIDC UserInfo
91 | // endpoint.
92 | type UserInfoClaims struct {
93 | Subject string `json:"sub,omitempty"`
94 | }
95 |
96 | // Valid implements the jwt.Claims interface.
97 | func (c UserInfoClaims) Valid() error {
98 | return nil
99 | }
100 |
101 | // SessionClaims define claims related to front end sessions, for example as
102 | // specified by https://openid.net/specs/openid-connect-frontchannel-1_0.html
103 | type SessionClaims struct {
104 | SessionID string `json:"sid,omitempty"`
105 | }
106 |
--------------------------------------------------------------------------------
/oidc/code/manager.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package code
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/identity"
22 | "stash.kopano.io/kc/konnect/oidc/payload"
23 | )
24 |
25 | // Record bundles the data storedi in a code manager.
26 | type Record struct {
27 | AuthenticationRequest *payload.AuthenticationRequest
28 | Auth identity.AuthRecord
29 | Session *payload.Session
30 | }
31 |
32 | // Manager is a interface defining a code manager.
33 | type Manager interface {
34 | Create(record *Record) (string, error)
35 | Pop(code string) (*Record, bool)
36 | }
37 |
--------------------------------------------------------------------------------
/oidc/code/managers/memorymap.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package managers
19 |
20 | import (
21 | "context"
22 | "time"
23 |
24 | "github.com/orcaman/concurrent-map"
25 | "stash.kopano.io/kgol/rndm"
26 |
27 | "stash.kopano.io/kc/konnect/oidc/code"
28 | )
29 |
30 | const (
31 | codeValidDuration = 2 * time.Minute
32 | )
33 |
34 | // Manager provides the api and state for OIDC code generation and token
35 | // exchange. The CodeManager's methods are safe to call from multiple Go
36 | // routines.
37 | type memoryMapManager struct {
38 | table cmap.ConcurrentMap
39 | codeDuration time.Duration
40 | }
41 |
42 | type codeRequestRecord struct {
43 | record *code.Record
44 | //ar *payload.AuthenticationRequest
45 | //auth identity.AuthRecord
46 | when time.Time
47 | }
48 |
49 | // NewMemoryMapManager creates a new CodeManager.
50 | func NewMemoryMapManager(ctx context.Context) code.Manager {
51 | cm := &memoryMapManager{
52 | table: cmap.New(),
53 | }
54 |
55 | // Cleanup function.
56 | go func() {
57 | ticker := time.NewTicker(30 * time.Second)
58 | defer ticker.Stop()
59 | for {
60 | select {
61 | case <-ticker.C:
62 | cm.purgeExpired()
63 | case <-ctx.Done():
64 | return
65 | }
66 |
67 | }
68 | }()
69 |
70 | return cm
71 | }
72 |
73 | func (cm *memoryMapManager) purgeExpired() {
74 | var expired []string
75 | deadline := time.Now().Add(-codeValidDuration)
76 | var record *codeRequestRecord
77 | for entry := range cm.table.IterBuffered() {
78 | record = entry.Val.(*codeRequestRecord)
79 | if record.when.Before(deadline) {
80 | expired = append(expired, entry.Key)
81 | }
82 | }
83 | for _, code := range expired {
84 | cm.table.Remove(code)
85 | }
86 | }
87 |
88 | // Create creates a new random code string, stores it together with the provided
89 | // values in the accociated CodeManager's table and returns the code.
90 | func (cm *memoryMapManager) Create(record *code.Record) (string, error) {
91 | code := rndm.GenerateRandomString(24)
92 |
93 | rr := &codeRequestRecord{
94 | record: record,
95 | when: time.Now(),
96 | }
97 | cm.table.Set(code, rr)
98 |
99 | return code, nil
100 | }
101 |
102 | // Pop looks up the provided code in the accociated CodeManagers's table. If
103 | // found it returns the authentication request and backend record plus true.
104 | // When not found, both values return as nil plus false.
105 | func (cm *memoryMapManager) Pop(code string) (*code.Record, bool) {
106 | stored, found := cm.table.Pop(code)
107 | if !found {
108 | return nil, false
109 | }
110 | rr := stored.(*codeRequestRecord)
111 |
112 | return rr.record, true
113 | }
114 |
--------------------------------------------------------------------------------
/oidc/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package oidc
19 |
20 | import (
21 | "fmt"
22 | "net/http"
23 |
24 | "stash.kopano.io/kc/konnect/utils"
25 | )
26 |
27 | // OAuth2Error defines a general OAuth2 error with id and decription.
28 | type OAuth2Error struct {
29 | ErrorID string `json:"error"`
30 | ErrorDescription string `json:"error_description"`
31 | }
32 |
33 | // Error implements the error interface.
34 | func (err *OAuth2Error) Error() string {
35 | return err.ErrorID
36 | }
37 |
38 | // Description implements the ErrorWithDescription interface.
39 | func (err *OAuth2Error) Description() string {
40 | return err.ErrorDescription
41 | }
42 |
43 | // NewOAuth2Error creates a new error with id and description.
44 | func NewOAuth2Error(id string, description string) utils.ErrorWithDescription {
45 | return &OAuth2Error{id, description}
46 | }
47 |
48 | // WriteWWWAuthenticateError writes the provided error with the provided
49 | // http status code to the provided http response writer as a
50 | // WWW-Authenticate header with comma separated fields for id and
51 | // description.
52 | func WriteWWWAuthenticateError(rw http.ResponseWriter, code int, err error) {
53 | if code == 0 {
54 | code = http.StatusUnauthorized
55 | }
56 |
57 | var description string
58 | switch err.(type) {
59 | case utils.ErrorWithDescription:
60 | description = err.(utils.ErrorWithDescription).Description()
61 | default:
62 | }
63 |
64 | rw.Header().Set("WWW-Authenticate", fmt.Sprintf("error=\"%s\", error_description=\"%s\"", err.Error(), description))
65 | rw.WriteHeader(code)
66 | }
67 |
68 | // IsErrorWithID returns true if the given error is an OAuth2Error error with
69 | // the given ID.
70 | func IsErrorWithID(err error, id string) bool {
71 | if err == nil {
72 | return false
73 | }
74 |
75 | oauth2Error, ok := err.(*OAuth2Error)
76 | if !ok {
77 | return false
78 | }
79 |
80 | return oauth2Error.ErrorID == id
81 | }
82 |
--------------------------------------------------------------------------------
/oidc/oidc.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package oidc
19 |
20 | // KonnectIDTokenSubjectSaltV1 is the salt value used when hasing Subjects in
21 | // ID tokens created by Konnect.
22 | const KonnectIDTokenSubjectSaltV1 = "konnect-IDToken-v1"
23 |
--------------------------------------------------------------------------------
/oidc/payload/request.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package payload
19 |
20 | import (
21 | "errors"
22 |
23 | "github.com/dgrijalva/jwt-go"
24 |
25 | "stash.kopano.io/kc/konnect/identity/clients"
26 | )
27 |
28 | // RequestObjectClaims holds the incoming request object claims provided as
29 | // JWT via request parameter to OpenID Connect 1.0 authorization endpoint
30 | // requests specified at
31 | // https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests
32 | type RequestObjectClaims struct {
33 | jwt.StandardClaims
34 |
35 | RawScope string `json:"scope"`
36 | Claims *ClaimsRequest `json:"claims"`
37 | RawResponseType string `json:"response_type"`
38 | ResponseMode string `json:"response_mode"`
39 | ClientID string `json:"client_id"`
40 | RawRedirectURI string `json:"redirect_uri"`
41 | State string `json:"state"`
42 | Nonce string `json:"nonce"`
43 | RawPrompt string `json:"prompt"`
44 | RawIDTokenHint string `json:"id_token_hint"`
45 | RawMaxAge string `json:"max_age"`
46 |
47 | RawRegistration string `json:"registration"`
48 |
49 | CodeChallenge string `json:"code_challenge"`
50 | CodeChallengeMethod string `json:"code_challenge_method"`
51 |
52 | client *clients.Secured
53 | }
54 |
55 | // SetSecure sets the provided client as owner of the accociated claims.
56 | func (roc *RequestObjectClaims) SetSecure(client *clients.Secured) error {
57 | if roc.ClientID != client.ID {
58 | return errors.New("client ID mismatch")
59 | }
60 |
61 | roc.client = client
62 |
63 | return nil
64 | }
65 |
66 | // Secure returns the accociated secure client or nil if not secure.
67 | func (roc *RequestObjectClaims) Secure() *clients.Secured {
68 | return roc.client
69 | }
70 |
--------------------------------------------------------------------------------
/oidc/payload/schema.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package payload
19 |
20 | import (
21 | "encoding/json"
22 | "reflect"
23 |
24 | "github.com/gorilla/schema"
25 | )
26 |
27 | var decoder = schema.NewDecoder()
28 | var encoder = schema.NewEncoder()
29 |
30 | // DecodeSchema decodes request form data into the provided dst schema struct.
31 | func DecodeSchema(dst interface{}, src map[string][]string) error {
32 | return decoder.Decode(dst, src)
33 | }
34 |
35 | // EncodeSchema encodes the provided src schema to the provided map.
36 | func EncodeSchema(src interface{}, dst map[string][]string) error {
37 | return encoder.Encode(src, dst)
38 | }
39 |
40 | // ConvertOIDCClaimsRequest is a converter function for oidc.ClaimsRequest data
41 | // provided in URL schema.
42 | func ConvertOIDCClaimsRequest(value string) reflect.Value {
43 | v := ClaimsRequest{}
44 |
45 | if err := json.Unmarshal([]byte(value), &v); err != nil {
46 | return reflect.Value{}
47 | }
48 |
49 | return reflect.ValueOf(v)
50 | }
51 |
52 | func init() {
53 | decoder.IgnoreUnknownKeys(true)
54 | decoder.RegisterConverter(ClaimsRequest{}, ConvertOIDCClaimsRequest)
55 | }
56 |
--------------------------------------------------------------------------------
/oidc/payload/session.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package payload
19 |
20 | // Session defines a Provider's session with a String identifier for a Session.
21 | // This represents a Session of a User Agent or device for a logged-in End-User
22 | // at an RP. Different ID values are used to identify distinct sessions. This
23 | // is implemented as defined in the OIDC Front Channel logout extension
24 | // https://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout
25 | type Session struct {
26 | Version int
27 | ID string
28 | Sub string
29 | Provider string
30 | }
31 |
--------------------------------------------------------------------------------
/oidc/payload/userinfo.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package payload
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/oidc"
22 | )
23 |
24 | // UserInfoResponse defines the data returned from the OIDC UserInfo
25 | // endpoint.
26 | type UserInfoResponse struct {
27 | oidc.UserInfoClaims
28 | *oidc.ProfileClaims
29 | *oidc.EmailClaims
30 | }
31 |
--------------------------------------------------------------------------------
/oidc/payload/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package payload
19 |
20 | import (
21 | "encoding/json"
22 | )
23 |
24 | // ToMap is a helper function to convert the provided payload struct to
25 | // a map type which can be used to extend the payload data with additional fields.
26 | func ToMap(payload interface{}) (map[string]interface{}, error) {
27 | // NOTE(longsleep): This implementation sucks, marshal to JSON and unmarshal
28 | // again - rly?
29 | intermediate, err := json.Marshal(payload)
30 | if err != nil {
31 | return nil, err
32 | }
33 | claims := make(map[string]interface{})
34 | err = json.Unmarshal(intermediate, &claims)
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | return claims, nil
40 | }
41 |
--------------------------------------------------------------------------------
/oidc/provider/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "time"
22 |
23 | "stash.kopano.io/kc/konnect/config"
24 | )
25 |
26 | // Config defines a Provider's configuration settings.
27 | type Config struct {
28 | Config *config.Config
29 |
30 | IssuerIdentifier string
31 | WellKnownPath string
32 | JwksPath string
33 | AuthorizationPath string
34 | TokenPath string
35 | UserInfoPath string
36 | EndSessionPath string
37 | CheckSessionIframePath string
38 | RegistrationPath string
39 |
40 | BrowserStateCookiePath string
41 | BrowserStateCookieName string
42 |
43 | SessionCookiePath string
44 | SessionCookieName string
45 |
46 | AccessTokenDuration time.Duration
47 | IDTokenDuration time.Duration
48 | RefreshTokenDuration time.Duration
49 | }
50 |
--------------------------------------------------------------------------------
/oidc/provider/cookie.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "net/http"
22 | )
23 |
24 | func (p *Provider) setBrowserStateCookie(rw http.ResponseWriter, value string) error {
25 | cookie := http.Cookie{
26 | Name: p.browserStateCookieName,
27 | Value: value,
28 |
29 | Path: p.browserStateCookiePath,
30 | Secure: true,
31 | HttpOnly: false, // This Cookie is intended to be read by Javascript.
32 | SameSite: http.SameSiteNoneMode,
33 | }
34 | http.SetCookie(rw, &cookie)
35 |
36 | return nil
37 | }
38 |
39 | func (p *Provider) removeBrowserStateCookie(rw http.ResponseWriter) error {
40 | cookie := http.Cookie{
41 | Name: p.browserStateCookieName,
42 |
43 | Path: p.browserStateCookiePath,
44 | Secure: true,
45 | HttpOnly: false, // This Cookie is intended to be read by Javascript.
46 | SameSite: http.SameSiteNoneMode,
47 |
48 | Expires: farPastExpiryTime,
49 | }
50 | http.SetCookie(rw, &cookie)
51 |
52 | return nil
53 | }
54 |
55 | func (p *Provider) setSessionCookie(rw http.ResponseWriter, value string) error {
56 | cookie := http.Cookie{
57 | Name: p.sessionCookieName,
58 | Value: value,
59 |
60 | Path: p.sessionCookiePath,
61 | Secure: true,
62 | HttpOnly: true,
63 | SameSite: http.SameSiteNoneMode,
64 | }
65 | http.SetCookie(rw, &cookie)
66 |
67 | return nil
68 | }
69 |
70 | func (p *Provider) getSessionCookie(req *http.Request) (string, error) {
71 | cookie, err := req.Cookie(p.sessionCookieName)
72 | if err != nil {
73 | return "", err
74 | }
75 |
76 | return cookie.Value, nil
77 | }
78 |
79 | func (p *Provider) removeSessionCookie(rw http.ResponseWriter) error {
80 | cookie := http.Cookie{
81 | Name: p.sessionCookieName,
82 |
83 | Path: p.sessionCookiePath,
84 | Secure: true,
85 | HttpOnly: true,
86 | SameSite: http.SameSiteNoneMode,
87 |
88 | Expires: farPastExpiryTime,
89 | }
90 | http.SetCookie(rw, &cookie)
91 |
92 | return nil
93 | }
94 |
--------------------------------------------------------------------------------
/oidc/provider/identity.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "errors"
22 |
23 | "github.com/dgrijalva/jwt-go"
24 |
25 | "stash.kopano.io/kc/konnect/identity"
26 | "stash.kopano.io/kc/konnect/oidc/payload"
27 | )
28 |
29 | func (p *Provider) getIdentityManager(identityProvider string) (identity.Manager, error) {
30 | if identityProvider == "" {
31 | // Return default manager when empty (backwards compatibility).
32 | return p.identityManager, nil
33 | }
34 |
35 | if identityProvider == p.identityManager.Name() {
36 | return p.identityManager, nil
37 | }
38 | if p.guestManager != nil && identityProvider == p.guestManager.Name() {
39 | return p.guestManager, nil
40 | }
41 |
42 | return nil, errors.New("identity provider mismatch")
43 | }
44 |
45 | func (p *Provider) getIdentityManagerFromClaims(identityProvider string, identityClaims jwt.MapClaims) (identity.Manager, error) {
46 | if identityClaims == nil {
47 | // Return default manager when no claims.
48 | return p.identityManager, nil
49 | }
50 |
51 | return p.getIdentityManager(identityProvider)
52 | }
53 |
54 | func (p *Provider) getIdentityManagerFromSession(session *payload.Session) (identity.Manager, error) {
55 | if session == nil {
56 | // Return default manager when no session.
57 | return p.identityManager, nil
58 | }
59 |
60 | return p.getIdentityManager(session.Provider)
61 | }
62 |
--------------------------------------------------------------------------------
/oidc/provider/signing.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "crypto"
22 |
23 | "github.com/dgrijalva/jwt-go"
24 | )
25 |
26 | // A SigningKey bundles a signer with meta data and a signign method.
27 | type SigningKey struct {
28 | ID string
29 | PrivateKey crypto.Signer
30 | SigningMethod jwt.SigningMethod
31 | }
32 |
--------------------------------------------------------------------------------
/oidc/provider/state.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "crypto/sha256"
22 | "encoding/base64"
23 | "encoding/hex"
24 | "fmt"
25 | "net/http"
26 | "net/url"
27 |
28 | "golang.org/x/crypto/blake2b"
29 | "stash.kopano.io/kgol/rndm"
30 |
31 | "stash.kopano.io/kc/konnect/identity"
32 | "stash.kopano.io/kc/konnect/oidc/payload"
33 | )
34 |
35 | var browserStateMarker = []byte("kopano-konnect-1")
36 |
37 | func (p *Provider) makeBrowserState(ar *payload.AuthenticationRequest, auth identity.AuthRecord, err error) (string, error) {
38 | hasher, hasherErr := blake2b.New256(nil)
39 | if hasherErr != nil {
40 | return "", hasherErr
41 | }
42 | if auth != nil && err == nil {
43 | hasher.Write([]byte(auth.Subject()))
44 | } else {
45 | // Use empty string value when not signed in or with error. This means
46 | // that a browser state is always created.
47 | hasher.Write([]byte(" "))
48 | }
49 | hasher.Write([]byte(" "))
50 | hasher.Write([]byte(p.issuerIdentifier))
51 | hasher.Write([]byte(" "))
52 | hasher.Write(browserStateMarker)
53 |
54 | // Encode to string.
55 | browserState := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
56 |
57 | return browserState, nil
58 | }
59 |
60 | func (p *Provider) makeSessionState(req *http.Request, ar *payload.AuthenticationRequest, browserState string) (string, error) {
61 | var origin string
62 |
63 | for {
64 | redirectURL := ar.RedirectURI
65 | if redirectURL != nil {
66 | origin = fmt.Sprintf("%s://%s", redirectURL.Scheme, redirectURL.Host)
67 | break
68 | }
69 |
70 | originHeader := req.Header.Get("Origin")
71 | if originHeader != "" {
72 | origin = originHeader
73 | break
74 | }
75 |
76 | refererHeader := req.Header.Get("Referer")
77 | if refererHeader != "" {
78 | // Rescure from referer.
79 | refererURL, err := url.Parse(refererHeader)
80 | if err != nil {
81 | return "", fmt.Errorf("invalid referer value: %v", err)
82 | }
83 |
84 | origin = fmt.Sprintf("%s://%s", refererURL.Scheme, refererURL.Host)
85 | break
86 | }
87 |
88 | return "", fmt.Errorf("missing origin")
89 | }
90 |
91 | salt := rndm.GenerateRandomString(32)
92 |
93 | hasher := sha256.New()
94 | hasher.Write([]byte(ar.ClientID))
95 | hasher.Write([]byte(" "))
96 | hasher.Write([]byte(origin))
97 | hasher.Write([]byte(" "))
98 | hasher.Write([]byte(browserState))
99 | hasher.Write([]byte(" "))
100 | hasher.Write([]byte(salt))
101 |
102 | sessionState := fmt.Sprintf("%s.%s", hex.EncodeToString(hasher.Sum(nil)), salt)
103 |
104 | return sessionState, nil
105 | }
106 |
--------------------------------------------------------------------------------
/oidc/provider/subject.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "errors"
22 |
23 | "stash.kopano.io/kc/konnect"
24 | "stash.kopano.io/kc/konnect/identity"
25 | )
26 |
27 | // PublicSubjectFromAuth creates the provideds auth Subject value with the
28 | // accociated provider. This subject can be used as URL safe value to uniquely
29 | // identify the provided auth user with remote systems.
30 | func (p *Provider) PublicSubjectFromAuth(auth identity.AuthRecord) (string, error) {
31 | authorizedScopes := auth.AuthorizedScopes()
32 | if ok, _ := authorizedScopes[konnect.ScopeRawSubject]; ok {
33 | // Return raw subject as is when with ScopeRawSubject.
34 | user := auth.User()
35 | if user == nil {
36 | return "", errors.New("no user")
37 | }
38 |
39 | return user.Raw(), nil
40 | }
41 |
42 | return auth.Subject(), nil
43 | }
44 |
--------------------------------------------------------------------------------
/oidc/provider/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package provider
19 |
20 | import (
21 | "fmt"
22 | "net/http"
23 | "net/url"
24 | "time"
25 | )
26 |
27 | var (
28 | farPastExpiryTime = time.Unix(0, 0)
29 | )
30 |
31 | func uniqueStrings(s []string) []string {
32 | var res []string
33 | m := make(map[string]bool)
34 | for _, s := range s {
35 | if _, ok := m[s]; ok {
36 | continue
37 | }
38 | m[s] = true
39 | res = append(res, s)
40 | }
41 |
42 | return res
43 | }
44 |
45 | func getRequestURL(req *http.Request, isTrustedSource bool) *url.URL {
46 | u, _ := url.Parse(req.URL.String())
47 |
48 | if isTrustedSource {
49 | // Look at proxy injected values to rewrite URLs if trusted.
50 | for {
51 | prefix := req.Header.Get("X-Forwarded-Prefix")
52 | if prefix != "" {
53 | u.Path = fmt.Sprintf("%s%s", prefix, u.Path)
54 | break
55 | }
56 | replaced := req.Header.Get("X-Replaced-Path")
57 | if replaced != "" {
58 | u.Path = replaced
59 | break
60 | }
61 |
62 | break
63 | }
64 | }
65 |
66 | return u
67 | }
68 |
69 | func addResponseHeaders(header http.Header) {
70 | header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
71 | header.Set("Pragma", "no-cache")
72 | header.Set("X-Content-Type-Options", "nosniff")
73 | header.Set("Referrer-Policy", "origin")
74 | }
75 |
76 | func makeArrayFromBoolMap(m map[string]bool) []string {
77 | result := []string{}
78 | for k, v := range m {
79 | if v {
80 | result = append(result, k)
81 | }
82 | }
83 |
84 | return result
85 | }
86 |
--------------------------------------------------------------------------------
/payloads.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package konnect
19 |
20 | import (
21 | "stash.kopano.io/kc/konnect/oidc/payload"
22 | )
23 |
24 | // UserInfoResponse defines the data returned from the Konnect UserInfo
25 | // endpoint. It is the standard ODIC response, extended with additional claims.
26 | type UserInfoResponse struct {
27 | *payload.UserInfoResponse
28 |
29 | *IDClaims
30 | *UniqueUserIDClaims
31 | }
32 |
--------------------------------------------------------------------------------
/provider.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package konnect
19 |
20 | import (
21 | "context"
22 |
23 | "stash.kopano.io/kc/konnect/identity"
24 | )
25 |
26 | // AccessTokenProvider is an interface for something which can create
27 | // access tokens.
28 | type AccessTokenProvider interface {
29 | MakeAccessToken(ctx context.Context, audience string, auth identity.AuthRecord) (string, error)
30 | }
31 |
--------------------------------------------------------------------------------
/scopes.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package konnect
19 |
20 | const (
21 | // ScopeID is the string value for the Konnect ID scope.
22 | ScopeID = "konnect/id"
23 | // ScopeUniqueUserID is the string value for the Konnect Unique User ID scope.
24 | ScopeUniqueUserID = "konnect/uuid"
25 |
26 | // ScopeRawSubject is the string value for the Konnect Raw Subject scope.
27 | ScopeRawSubject = "konnect/raw_sub"
28 |
29 | // ScopeGuestOK is the string value for the Konnect Guest OK scope.
30 | ScopeGuestOK = "konnect/guestok"
31 | )
32 |
--------------------------------------------------------------------------------
/scopes.yaml.in:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | mapping:
4 | # old-scope: custom-scope
5 |
6 | scopes:
7 | # custom-scope:
8 | # description: "This is the a custom scope"
9 | # priority: 100
10 |
11 | # another-scope:
12 | # description: "This is the another scope"
13 |
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | # Scripts and helpers for Kopano Konnect
2 |
3 | This folder contains various scripts and helpers to integrate Konnect with
4 | various ways how it can be started and configured.
5 |
6 | ## Rkt
7 |
8 | Rkt is a pod-native container engine for Linux. Here are some examples how to
9 | integrate/run konnect with rkt. The following section is kept here as example.
10 |
11 | ### Config file as systemd environment file
12 |
13 | ```
14 | # OpenID Connect Issuer Identifier
15 | ISS=https://konnect.local
16 |
17 | # Full file path to a PEM encoded PKCS#1 or PKCS#5 private key which is used to
18 | # sign tokens. This file must exist and be valid to be able to start the
19 | # service. A suitable key can be generated with:
20 | # `openssl genpkey -algorithm RSA \
21 | # -out konnectd-signing-private-key.pem.pem \
22 | # -pkeyopt rsa_keygen_bits:4096`
23 | SIGNING_PRIVATE_KEY=/etc/kopano/konnectd-signing-private-key.pem
24 |
25 | # Full file path to a encryption secret key file containing random bytes. This
26 | # file must exist to be able to start the service. A suitable file can be
27 | # generated with:
28 | # `openssl rand -out konnectd-encryption.key 32`
29 | ENCRYPTION_SECRET=/etc/kopano/konnectd-encryption.key
30 |
31 | # Full file path to the identifier registration configuration file. This file
32 | # must exist to be able to start the service. An example file is shipped with
33 | # the documentation / sources.
34 | IDENTIFIER_REGISTRATION_CONF=/etc/kopano/konnectd-identifier-registration.yaml
35 |
36 | # Identity manager Kopano Core via direct connection.
37 | IDENTITY_MANAGER=kc
38 | KOPANO_SERVER_DEFAULT_URI=file:///run/kopano-server.sock
39 | KOPANO_SERVER_USERNAME=SYSTEM
40 | KOPANO_SERVER_PASSWORD=
41 |
42 | # Identity manager Kopano Webapp via Cookie pass through.
43 | #SIGN_IN_URL=https://your-kopano.local/webapp/index.php
44 | #IDENTITY_MANAGER=cookie "$SIGN_IN_URL?load=custom&name=oidcuser" KOPANO_WEBAPP encryption-store-key
45 |
46 | #IMAGE=/srv/images/kopano-konnectd-latest-linux-amd64.aci
47 | #RKT_ARGS=--port=www:127.0.0.1:8777
48 | #ARGS=--insecure
49 | ```
50 |
51 | ### Systemd service with rkt
52 |
53 | ```
54 | [Unit]
55 | Description=kopano-konnectd
56 | Requires=network-online.target
57 | After=network-online.target
58 |
59 | [Service]
60 | Slice=machine.slice
61 | Environment=RKT_ARGS=--port=www:127.0.0.1:8777
62 | Environment=ISS=https://konnect.local
63 | Environment=SIGNING_PRIVATE_KEY=/etc/kopano/konnectd-tokens-signing-key.pem
64 | Environment=ENCRYPTION_SECRET=/etc/kopano/konnectd-encryption.key
65 | Environment=IDENTIFIER_REGISTRATION_CONF=/etc/kopano/konnectd-identifier-registration.yaml
66 | Environment=IMAGE=/srv/images/kopano-konnectd-latest-linux-amd64.aci
67 | EnvironmentFile=/etc/default/kopano-konnectd
68 | ExecStart=/usr/bin/rkt \
69 | --insecure-options=image \
70 | run $RKT_ARGS \
71 | --volume signing-private-key,kind=host,source=${SIGNING_PRIVATE_KEY} \
72 | --volume encryption-secret,kind=host,source=${ENCRYPTION_SECRET} \
73 | --volume identifier-registration-conf,kind=host,source=${IDENTIFIER_REGISTRATION_CONF} \
74 | --volume etc-ssl-certs,kind=host,source=/etc/ssl/certs \
75 | --volume run,kind=host,source=/run \
76 | ${IMAGE} \
77 | --environment=KOPANO_SERVER_DEFAULT_URI="$KOPANO_SERVER_DEFAULT_URI" \
78 | --environment=KOPANO_SERVER_USERNAME="$KOPANO_SERVER_USERNAME" \
79 | --environment=KOPANO_SERVER_PASSWORD="$KOPANO_SERVER_PASSWORD" \
80 | -- \
81 | --iss=${ISS} \
82 | --sign-in-uri=${SIGN_IN_URL} \
83 | $ARGS $IDENTITY_MANAGER
84 | ExecStopPost=/usr/bin/rkt gc --mark-only
85 | KillMode=mixed
86 | Restart=always
87 | ```
88 |
--------------------------------------------------------------------------------
/scripts/build-konnectd-aci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | BINARY=bin/konnectd
4 | IDENTIFIER_WEBAPP_BUILD=identifier/build
5 | HOSTOS=$(go env GOHOSTOS)
6 | ARCH=$(go env GOHOSTARCH)
7 |
8 | make
9 | VERSION=$($BINARY version|grep Version|awk -F': ' '{ print $2 }')
10 | (cd identifier && make)
11 |
12 | acbuild --debug begin
13 | trap "{ export EXT=$?; acbuild --debug end && rm -rf $TMPDIR && exit $EXT; }" EXIT
14 |
15 | acbuild --debug set-name kopano.com/konnectd
16 | acbuild --debug set-user 301
17 | acbuild --debug set-group 301
18 | acbuild --debug copy $BINARY /srv/konnectd
19 | acbuild --debug copy $IDENTIFIER_WEBAPP_BUILD /srv/identifier-webapp
20 | acbuild --debug set-exec -- /srv/konnectd serve \
21 | --listen=0.0.0.0:8777 \
22 | --signing-private-key=/signing-private-key.pem \
23 | --encryption-secret=/encryption.key \
24 | --identifier-registration-conf=/identifier-registration.yaml \
25 | --identifier-client-path=/srv/identifier-webapp
26 | acbuild --debug port add www tcp 8777
27 | acbuild --debug mount add signing-private-key /signing-private-key.pem --read-only
28 | acbuild --debug mount add encryption-secret /encryption.key --read-only
29 | acbuild --debug mount add identifier-registration-conf /identifier-registration.yaml --read-only
30 | acbuild --debug mount add etc-ssl-certs /etc/ssl/certs --read-only
31 | acbuild --debug mount add run /run
32 | acbuild --debug label add version $VERSION
33 | acbuild --debug label add arch $ARCH
34 | acbuild --debug label add os $HOSTOS
35 | acbuild --debug write --overwrite kopano-konnectd-$VERSION-$HOSTOS-$ARCH.aci
36 |
--------------------------------------------------------------------------------
/scripts/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright 2017-2019 Kopano and its licensors
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | set -euo pipefail
19 |
20 | # Check for parameters, prepend with our exe when the first arg is a parameter.
21 | if [ "${1:0:1}" = '-' ]; then
22 | set -- ${EXE} "$@"
23 | else
24 | # Check for some basic commands, this is used to allow easy calling without
25 | # having to prepend the binary all the time.
26 | case "${1}" in
27 | help|version)
28 | set -- ${EXE} "$@"
29 | ;;
30 |
31 | serve)
32 | shift
33 | set -- ${EXE} serve --metrics-listen=0.0.0.0:6777 "$@"
34 | ;;
35 | esac
36 | fi
37 |
38 | # Setup environment.
39 | setup_env() {
40 | [ -f /etc/defaults/docker-env ] && source /etc/defaults/docker-env
41 | }
42 | setup_env
43 |
44 | # Support additional args provided via environment.
45 | if [ -n "${ARGS}" ]; then
46 | set -- "$@" ${ARGS}
47 | fi
48 |
49 | # Run the service, optionally switching user when running as root.
50 | if [ $(id -u) = 0 -a -n "${KONNECTD_USER}" ]; then
51 | userAndgroup="${KONNECTD_USER}"
52 | if [ -n "${KONNECTD_GROUP}" ]; then
53 | userAndgroup="${userAndgroup}:${KONNECTD_GROUP}"
54 | fi
55 | exec su-exec ${userAndgroup} "$@"
56 | else
57 | exec "$@"
58 | fi
59 |
--------------------------------------------------------------------------------
/scripts/healthcheck.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright 2017-2019 Kopano and its licensors
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 | set -euo pipefail
19 |
20 | set -- ${EXE} healthcheck "$@"
21 |
22 | exec "$@"
23 |
--------------------------------------------------------------------------------
/scripts/kopano-konnectd.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Kopano Konnect Daemon
3 |
4 | [Service]
5 | Type=simple
6 | PrivateTmp=yes
7 | User=konnect
8 | Group=kopano
9 | NoNewPrivileges=yes
10 | PrivateUsers=yes
11 | CapabilityBoundingSet=
12 | ProtectSystem=full
13 | UMask=0077
14 | LimitNOFILE=infinity
15 | PermissionsStartOnly=true
16 | Environment=LC_CTYPE=en_US.UTF-8
17 | EnvironmentFile=-/etc/kopano/konnectd.cfg
18 | ExecStartPre=/usr/sbin/kopano-konnectd setup
19 | ExecStart=/usr/sbin/kopano-konnectd serve --log-timestamp=false
20 |
21 | [Install]
22 | WantedBy=multi-user.target
23 |
--------------------------------------------------------------------------------
/server/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package server
19 |
20 | import (
21 | "context"
22 | "net/http"
23 |
24 | "github.com/gorilla/mux"
25 |
26 | "stash.kopano.io/kc/konnect/config"
27 | )
28 |
29 | // Config defines a Server's configuration settings.
30 | type Config struct {
31 | Config *config.Config
32 |
33 | Handler http.Handler
34 | Routes []WithRoutes
35 | }
36 |
37 | // WithRoutes provide http routing within a context.
38 | type WithRoutes interface {
39 | AddRoutes(ctx context.Context, router *mux.Router)
40 | }
41 |
--------------------------------------------------------------------------------
/server/handlers.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package server
19 |
20 | import (
21 | "net/http"
22 | )
23 |
24 | // HealthCheckHandler a http handler return 200 OK when server health is fine.
25 | func (s *Server) HealthCheckHandler(rw http.ResponseWriter, req *http.Request) {
26 | rw.WriteHeader(http.StatusOK)
27 | }
28 |
--------------------------------------------------------------------------------
/server/handlers_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package server
19 |
20 | import (
21 | "context"
22 | "net/http"
23 | "net/http/httptest"
24 | "testing"
25 | )
26 |
27 | func TestHealthCheckHandler(t *testing.T) {
28 | ctx, cancel := context.WithCancel(context.Background())
29 | defer cancel()
30 |
31 | // Create our server.
32 | httpServer, _, router, _ := newTestServer(ctx, t)
33 | defer httpServer.Close()
34 |
35 | // Prepare the request to pass to our handler.
36 | req, err := http.NewRequest("GET", "/health-check", nil)
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 |
41 | // Create response recorder to record the response.
42 | rr := httptest.NewRecorder()
43 | router.ServeHTTP(rr, req)
44 |
45 | // Check the status code is what we expect.
46 | if status := rr.Code; status != http.StatusOK {
47 | t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/server/server_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package server
19 |
20 | import (
21 | "context"
22 | "net/http"
23 | "net/http/httptest"
24 | "os"
25 | "testing"
26 |
27 | "github.com/gorilla/mux"
28 | "github.com/sirupsen/logrus"
29 |
30 | "stash.kopano.io/kc/konnect/config"
31 | "stash.kopano.io/kc/konnect/identity"
32 | "stash.kopano.io/kc/konnect/identity/clients"
33 | identityManagers "stash.kopano.io/kc/konnect/identity/managers"
34 | "stash.kopano.io/kc/konnect/managers"
35 | codeManagers "stash.kopano.io/kc/konnect/oidc/code/managers"
36 | "stash.kopano.io/kc/konnect/oidc/provider"
37 | )
38 |
39 | var logger = &logrus.Logger{
40 | Out: os.Stderr,
41 | Formatter: &logrus.TextFormatter{DisableColors: true},
42 | Level: logrus.DebugLevel,
43 | }
44 |
45 | func newTestServer(ctx context.Context, t *testing.T) (*httptest.Server, *Server, http.Handler, *config.Config) {
46 | mgrs := managers.New()
47 | mgrs.Set("identity", identityManagers.NewDummyIdentityManager(
48 | &identity.Config{},
49 | "unittestuser",
50 | ))
51 | mgrs.Set("code", codeManagers.NewMemoryMapManager(ctx))
52 | encryptionManager, _ := identityManagers.NewEncryptionManager(nil)
53 | mgrs.Set("encryption", encryptionManager)
54 | mgrs.Set("clients", &clients.Registry{})
55 |
56 | cfg := &config.Config{
57 | Logger: logger,
58 | }
59 |
60 | p, err := provider.NewProvider(&provider.Config{
61 | Config: cfg,
62 |
63 | IssuerIdentifier: "http://localhost:8777",
64 | WellKnownPath: "/.well-known/openid-configuration",
65 | JwksPath: "/konnect/v1/jwks.json",
66 | AuthorizationPath: "/konnect/v1/authorize",
67 | TokenPath: "/konnect/v1/token",
68 | UserInfoPath: "/konnect/v1/userinfo",
69 | })
70 | if err != nil {
71 | t.Fatal(err)
72 | }
73 | err = p.RegisterManagers(mgrs)
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | server, err := NewServer(&Config{
79 | Config: cfg,
80 |
81 | Handler: p,
82 | })
83 | if err != nil {
84 | t.Fatal(err)
85 | }
86 | router := mux.NewRouter()
87 | server.AddRoutes(ctx, router)
88 |
89 | s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
90 | router.ServeHTTP(rw, req)
91 | }))
92 |
93 | return s, server, router, cfg
94 | }
95 |
96 | func TestNewTestServer(t *testing.T) {
97 | ctx, cancel := context.WithCancel(context.Background())
98 | defer cancel()
99 | newTestServer(ctx, t)
100 | }
101 |
--------------------------------------------------------------------------------
/signing/jwk.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package signing
19 |
20 | import (
21 | "crypto"
22 | "encoding/base64"
23 |
24 | jwk "github.com/mendsley/gojwk"
25 | "golang.org/x/crypto/ed25519"
26 | )
27 |
28 | // JWKFromPublicKey creates a JWK from a public key
29 | func JWKFromPublicKey(key crypto.PublicKey) (*jwk.Key, error) {
30 | switch key := key.(type) {
31 | case ed25519.PublicKey:
32 | jwt := &jwk.Key{
33 | Kty: "OKP",
34 | Crv: "Ed25519",
35 | X: base64.RawURLEncoding.EncodeToString(key),
36 | }
37 |
38 | return jwt, nil
39 |
40 | default:
41 | return jwk.PublicKey(key)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/signing/jwt.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package signing
19 |
20 | import (
21 | "crypto"
22 | "crypto/rand"
23 | "errors"
24 |
25 | "github.com/dgrijalva/jwt-go"
26 | "golang.org/x/crypto/ed25519"
27 | )
28 |
29 | // Errors used by this package.
30 | var (
31 | ErrEdDSAVerification = errors.New("eddsa: verification error")
32 | )
33 |
34 | // SigningMethodEdwardsCurve implements the EdDSA family of signing methods.
35 | type SigningMethodEdwardsCurve struct {
36 | Name string
37 | }
38 |
39 | // Specific instances for EdDSA
40 | var (
41 | SigningMethodEdDSA *SigningMethodEdwardsCurve
42 | )
43 |
44 | func init() {
45 | // EdDSA with Ed25519 https://tools.ietf.org/html/rfc8037#section-3.1.
46 | SigningMethodEdDSA = &SigningMethodEdwardsCurve{"EdDSA"}
47 | jwt.RegisterSigningMethod(SigningMethodEdDSA.Alg(), func() jwt.SigningMethod {
48 | return SigningMethodEdDSA
49 | })
50 | }
51 |
52 | // Alg implements the jwt.SigningMethod interface.
53 | func (m *SigningMethodEdwardsCurve) Alg() string {
54 | return m.Name
55 | }
56 |
57 | // Verify implements the jwt.SigningMethod interface.
58 | func (m *SigningMethodEdwardsCurve) Verify(signingString, signature string, key interface{}) error {
59 | var err error
60 |
61 | // Decode the signature
62 | var sig []byte
63 | if sig, err = jwt.DecodeSegment(signature); err != nil {
64 | return err
65 | }
66 |
67 | // Get the key
68 | switch k := key.(type) {
69 | case ed25519.PublicKey:
70 | if len(k) != ed25519.PublicKeySize {
71 | return jwt.ErrInvalidKey
72 | }
73 | if verifystatus := ed25519.Verify(k, []byte(signingString), sig); verifystatus == true {
74 | return nil
75 | } else {
76 | return ErrEdDSAVerification
77 | }
78 |
79 | default:
80 | return jwt.ErrInvalidKeyType
81 | }
82 | }
83 |
84 | // Sign implements the jwt.SigningMethod interface.
85 | func (m *SigningMethodEdwardsCurve) Sign(signingString string, key interface{}) (string, error) {
86 | switch k := key.(type) {
87 | case ed25519.PrivateKey:
88 | if len(k) != ed25519.PrivateKeySize {
89 | return "", jwt.ErrInvalidKey
90 | }
91 | if s, err := k.Sign(rand.Reader, []byte(signingString), crypto.Hash(0)); err == nil {
92 | // We serialize the signature.
93 | return jwt.EncodeSegment(s), nil
94 | } else {
95 | return "", err
96 | }
97 |
98 | default:
99 | return "", jwt.ErrInvalidKeyType
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/utils/errorpage.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "fmt"
22 | "net/http"
23 | )
24 |
25 | // WriteErrorPage create a formatted error page response containing the provided
26 | // information and writes it to the provided http.ResponseWriter.
27 | func WriteErrorPage(rw http.ResponseWriter, code int, title string, message string) {
28 | if title == "" {
29 | title = http.StatusText(code)
30 | }
31 |
32 | text := fmt.Sprintf("%d %s", code, title)
33 | if message != "" {
34 | text = text + " - " + message
35 | }
36 |
37 | http.Error(rw, text, code)
38 | }
39 |
--------------------------------------------------------------------------------
/utils/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "fmt"
22 | )
23 |
24 | // ErrorWithDescription is an interface binding the standard error
25 | // inteface with a description.
26 | type ErrorWithDescription interface {
27 | error
28 | Description() string
29 | }
30 |
31 | // DescribeError returns a wrapped version for errors which contain additional
32 | // fields. The wrapped version contains all fields as a string value. Use this
33 | // for general purpose logging of rich errors.
34 | func DescribeError(err error) error {
35 | switch err.(type) {
36 | case ErrorWithDescription:
37 | err = fmt.Errorf("%s - %s", err, err.(ErrorWithDescription).Description())
38 | }
39 |
40 | return err
41 | }
42 |
43 | // ErrorAsFields returns a mapping of all fields of the provided error.
44 | func ErrorAsFields(err error) map[string]interface{} {
45 | if err == nil {
46 | return nil
47 | }
48 |
49 | fields := make(map[string]interface{})
50 | fields["error"] = err.Error()
51 | switch err.(type) {
52 | case ErrorWithDescription:
53 | fields["desc"] = err.(ErrorWithDescription).Description()
54 | }
55 |
56 | return fields
57 | }
58 |
--------------------------------------------------------------------------------
/utils/http.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "crypto/tls"
22 | "net"
23 | "net/http"
24 | "time"
25 |
26 | "golang.org/x/net/http2"
27 |
28 | "stash.kopano.io/kc/konnect/version"
29 | )
30 |
31 | const (
32 | defaultHTTPTimeout = 30 * time.Second
33 | defaultHTTPKeepAlive = 30 * time.Second
34 | defaultHTTPMaxIdleConns = 100
35 | defaultHTTPIdleConnTimeout = 90 * time.Second
36 | defaultHTTPTLSHandshakeTimeout = 10 * time.Second
37 | defaultHTTPExpectContinueTimeout = 1 * time.Second
38 | )
39 |
40 | // DefaultHTTPUserAgent is the User-Agent Header which should be used when
41 | // making HTTP requests.
42 | var DefaultHTTPUserAgent = "Kopano-Konnect/" + version.Version
43 |
44 | // HTTPTransportWithTLSClientConfig creates a new http.Transport with sane
45 | // default settings using the provided tls.Config.
46 | func HTTPTransportWithTLSClientConfig(tlsClientConfig *tls.Config) *http.Transport {
47 | transport := &http.Transport{
48 | Proxy: http.ProxyFromEnvironment,
49 | DialContext: (&net.Dialer{
50 | Timeout: defaultHTTPTimeout,
51 | KeepAlive: defaultHTTPKeepAlive,
52 | DualStack: true,
53 | }).DialContext,
54 | MaxIdleConns: defaultHTTPMaxIdleConns,
55 | IdleConnTimeout: defaultHTTPIdleConnTimeout,
56 | TLSHandshakeTimeout: defaultHTTPTLSHandshakeTimeout,
57 | ExpectContinueTimeout: defaultHTTPExpectContinueTimeout,
58 | }
59 | if tlsClientConfig != nil {
60 | transport.TLSClientConfig = tlsClientConfig
61 | err := http2.ConfigureTransport(transport)
62 | if err != nil {
63 | panic(err)
64 | }
65 | }
66 |
67 | return transport
68 | }
69 |
70 | // DefaultTLSConfig returns a new tls.Config.
71 | func DefaultTLSConfig() *tls.Config {
72 | return &tls.Config{
73 | ClientSessionCache: tls.NewLRUClientSessionCache(0),
74 | }
75 | }
76 |
77 | // InsecureSkipVerifyTLSConfig returns a new tls.Config which does skip TLS verification.
78 | func InsecureSkipVerifyTLSConfig() *tls.Config {
79 | config := DefaultTLSConfig()
80 | config.InsecureSkipVerify = true
81 |
82 | return config
83 | }
84 |
85 | // DefaultHTTPClient is a http.Client with a timeout set.
86 | var DefaultHTTPClient = &http.Client{
87 | Timeout: defaultHTTPTimeout,
88 | Transport: HTTPTransportWithTLSClientConfig(DefaultTLSConfig()),
89 | }
90 |
91 | // InsecureHTTPClient is a http.Client with a timeout set and with TLS
92 | // verification disabled.
93 | var InsecureHTTPClient = &http.Client{
94 | Timeout: defaultHTTPTimeout,
95 | Transport: HTTPTransportWithTLSClientConfig(InsecureSkipVerifyTLSConfig()),
96 | }
97 |
--------------------------------------------------------------------------------
/utils/json.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "encoding/json"
22 | "net/http"
23 | )
24 |
25 | const (
26 | defaultJSONContentType = "application/json; encoding=utf-8"
27 | )
28 |
29 | // WriteJSON marshals the provided data as JSON and writes it to the provided
30 | // http.ResponseWriter using the provided HTTP status code and content-type. the
31 | // nature of this function is that it always writes a HTTP response header. Thus
32 | // it makes no sense to write another header on error. Resulting errors should
33 | // be logged and the connection should be closes as it is non-functional.
34 | func WriteJSON(rw http.ResponseWriter, code int, data interface{}, contentType string) error {
35 | if contentType == "" {
36 | rw.Header().Set("Content-Type", defaultJSONContentType)
37 | } else {
38 | rw.Header().Set("content-Type", contentType)
39 | }
40 |
41 | rw.WriteHeader(code)
42 |
43 | enc := json.NewEncoder(rw)
44 | enc.SetIndent("", " ")
45 |
46 | return enc.Encode(data)
47 | }
48 |
--------------------------------------------------------------------------------
/utils/origin.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "net/http"
22 | "net/url"
23 | )
24 |
25 | // OriginFromRequestHeaders tries to find information about the origin from the
26 | // provided http.Header. It first looks into the Origin header field and if that
27 | // is not found it looks into the Referer header field. If both are not found
28 | // an empty string is returned.
29 | func OriginFromRequestHeaders(header http.Header) string {
30 | origin := header.Get("Origin")
31 | if origin == "" {
32 | referer := header.Get("Referer")
33 | if referer != "" {
34 | refererURI, _ := url.Parse(referer)
35 | origin = refererURI.Scheme + "://" + refererURI.Host
36 | }
37 | }
38 |
39 | return origin
40 | }
41 |
--------------------------------------------------------------------------------
/utils/redirect.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "net/http"
22 | "net/url"
23 | "strings"
24 |
25 | "github.com/google/go-querystring/query"
26 | )
27 |
28 | // WriteRedirect crates a URL out of the provided uri and params and writes a
29 | // a HTTP response with the provided HTTP status code to the provided
30 | // http.ResponseWriter incliding HTTP caching headers to prevent caching. If
31 | // asFragment is true, the provided params are added as URL fragment, otherwise
32 | // they replace the query. If params is nil, the provided uri is taken as is.
33 | func WriteRedirect(rw http.ResponseWriter, code int, uri *url.URL, params interface{}, asFragment bool) error {
34 | if params != nil {
35 | paramValues, err := query.Values(params)
36 | if err != nil {
37 | return err
38 | }
39 |
40 | u, _ := url.Parse(uri.String())
41 | if asFragment {
42 | if u.Fragment != "" {
43 | u.Fragment += "&"
44 | }
45 | f := paramValues.Encode() // This encods into URL encoded form with QueryEscape.
46 | f, _ = url.QueryUnescape(f) // But we need it unencoded since its the fragment, it is encoded later (when serializing the URL).
47 | u.Fragment += f // Append fragment extension.
48 | } else {
49 | queryValues := u.Query()
50 | for k, vs := range paramValues {
51 | for _, v := range vs {
52 | queryValues.Add(k, v)
53 | }
54 | }
55 | u.RawQuery = strings.ReplaceAll(queryValues.Encode(), "+", "%20") // NOTE(longsleep): Ensure we use %20 instead of +.
56 | }
57 | uri = u
58 | }
59 |
60 | rw.Header().Set("Location", uri.String())
61 | rw.Header().Set("Cache-Control", "no-store")
62 | rw.Header().Set("Pragma", "no-cache")
63 |
64 | rw.WriteHeader(code)
65 |
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/utils/schema.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "github.com/gorilla/schema"
22 | )
23 |
24 | // Create a Decoder instance as a package global, because it caches
25 | // meta-data about structs, and an instance can be shared safely.
26 | var urlSchemaDecoder = schema.NewDecoder()
27 |
28 | // DecodeURLSchema decodes request for mdata in to the provided dst url struct.
29 | func DecodeURLSchema(dst interface{}, src map[string][]string) error {
30 | return urlSchemaDecoder.Decode(dst, src)
31 | }
32 |
33 | func init() {
34 | urlSchemaDecoder.SetAliasTag("url")
35 | urlSchemaDecoder.IgnoreUnknownKeys(true)
36 | }
37 |
--------------------------------------------------------------------------------
/utils/trusted.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
20 | import (
21 | "net"
22 | "net/http"
23 | )
24 |
25 | // IsRequestFromTrustedSource checks if the provided requests remote address is
26 | // one either one of the provided ips or in one of the provided networks.
27 | func IsRequestFromTrustedSource(req *http.Request, ips []*net.IP, nets []*net.IPNet) (bool, error) {
28 | ipString, _, err := net.SplitHostPort(req.RemoteAddr)
29 | if err != nil {
30 | return false, err
31 | }
32 |
33 | ip := net.ParseIP(ipString)
34 |
35 | for _, checkIP := range ips {
36 | if checkIP.Equal(ip) {
37 | return true, nil
38 | }
39 | }
40 |
41 | for _, checkNet := range nets {
42 | if checkNet.Contains(ip) {
43 | return true, nil
44 | }
45 | }
46 |
47 | return false, nil
48 | }
49 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package utils
19 |
--------------------------------------------------------------------------------
/version/version.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2019 Kopano and its licensors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package version
19 |
20 | // BuildDate defines the date when build/compile was run. This will be filled in
21 | // by the compiler.
22 | var BuildDate string
23 |
24 | // Version defines the main version number that is being run at the moment. This
25 | // will be filled by the compiler.
26 | var Version = "0.0.0-no-proper-build"
27 |
--------------------------------------------------------------------------------