/
index.go
196 lines (159 loc) · 5.33 KB
/
index.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package handlers
import (
"fmt"
"log"
"strings"
"time"
"crypto/tls"
"net/http"
"github.com/dgrijalva/jwt-go"
"github.com/freitagsrunde/protokollamt/middleware"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/ldap.v2"
)
// SessionCreater specifies what functionality
// is needed to communicate with and authenticate
// against configured LDAP service, and for creating
// signed session objects.
type SessionCreater interface {
GetJWTSigningSecret() string
GetJWTValidFor() time.Duration
GetLDAPServiceAddr() string
GetLDAPServerName() string
GetLDAPBindDN() string
}
// LoginPayload represents the values an user
// can supply to protokollamt in order to
// authenticate against configured LDAP.
type LoginPayload struct {
Name string `form:"login-name"`
Password string `form:"login-password"`
}
// CreateSession produces a JSON Web Token (JWT) with
// authenticated claims based on supplied user values
// and saves it inside the session storage.
func CreateSession(name string, jwtSignSecret []byte, jwtValidFor time.Duration) (string, error) {
// Save current timestamp.
nowTime := time.Now()
expTime := nowTime.Add(jwtValidFor)
// Create a JWT with claims to identify user.
token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
"iss": name,
"iat": nowTime.Unix(),
"nbf": nowTime.Add((-1 * time.Minute)).Unix(),
"exp": expTime.Unix(),
})
// Obtain the signed string.
signedToken, err := token.SignedString(jwtSignSecret)
if err != nil {
return "", fmt.Errorf("failed to create signed JWT string: %v", err)
}
return signedToken, nil
}
// Index delivers the first page of protokollamt,
// a login form to authenticate via LDAP.
func Index() gin.HandlerFunc {
return func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
})
}
}
// IndexLogin accepts user supplied LDAP credentials,
// asks the LDAP service to verify them, and creates
// a new session for respective user.
func IndexLogin(sessCreater SessionCreater) gin.HandlerFunc {
return func(c *gin.Context) {
var Payload LoginPayload
// Parse login form data into above defined payload.
err := c.BindWith(&Payload, binding.FormPost)
if err != nil {
c.HTML(http.StatusBadRequest, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Verarbeitungsfehler. Bitte erneut versuchen.",
})
c.Abort()
return
}
Payload.Name = strings.TrimSpace(Payload.Name)
// Connect to LDAP service configured in
// protokollamt's config file.
l, err := ldap.Dial("tcp", sessCreater.GetLDAPServiceAddr())
if err != nil {
log.Printf("Connecting to configured LDAP service failed: %v", err)
c.HTML(http.StatusInternalServerError, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Verarbeitungsfehler. Bitte erneut versuchen.",
})
c.Abort()
return
}
defer l.Close()
// Upgrade current unencrypted session with
// LDAP service to TLS with StartTLS.
err = l.StartTLS(&tls.Config{
ServerName: sessCreater.GetLDAPServerName(),
InsecureSkipVerify: false,
})
if err != nil {
log.Printf("Upgrading LDAP connection to TLS failed: %v", err)
c.HTML(http.StatusInternalServerError, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Verarbeitungsfehler. Bitte erneut versuchen.",
})
c.Abort()
return
}
// Bind with name of user and password supplied
// via validated login form values.
err = l.Bind(fmt.Sprintf("uid=%s,%s", Payload.Name, sessCreater.GetLDAPBindDN()), Payload.Password)
if err != nil {
// Check if user supplied invalid credentials.
if strings.Contains(err.Error(), "Code 49 \"Invalid Credentials\"") {
c.HTML(http.StatusBadRequest, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Benutzername oder Passwort falsch.",
})
c.Abort()
return
}
// If not, this is an internal error.
log.Printf("Binding with supplied user name and password failed: %v", err)
c.HTML(http.StatusInternalServerError, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Verarbeitungsfehler. Bitte erneut versuchen.",
})
c.Abort()
return
}
// Create a new session and obtain a signed JWT.
token, err := CreateSession(Payload.Name, []byte(sessCreater.GetJWTSigningSecret()), sessCreater.GetJWTValidFor())
if err != nil {
log.Printf("JWT creation failure: %v", err)
c.HTML(http.StatusInternalServerError, "index.html", gin.H{
"PageTitle": "Protokollamt der Freitagsrunde",
"MainTitle": "Protokollamt",
"FatalError": "Verarbeitungsfehler. Bitte erneut versuchen.",
})
c.Abort()
return
}
// Insert cookie into client's headers.
c.SetCookie("Protokollamt", token, int(sessCreater.GetJWTValidFor()), "", "", false, true)
c.Redirect(http.StatusFound, "/protocols")
}
}
// IndexLogout destroys the active session of
// requesting user, logging the user out.
func IndexLogout() gin.HandlerFunc {
return func(c *gin.Context) {
middleware.CookieRemoveRedirect(c)
}
}