• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 1797192453

02 May 2025 09:57AM UTC coverage: 65.257% (-0.01%) from 65.268%
1797192453

Pull #621

gitlab-ci

alfrunes
fix: Ensure email is always encoded in lowercase when stored

Added a bson codec for model.Email that will ensure that emails are
always encoded in lowercase in the database to ensure case insensitive
queries.

Changelog: Title
Ticket: MEN-8328
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #621: fix: Ensure email is always encoded in lowercase when stored

7 of 13 new or added lines in 2 files covered. (53.85%)

3 existing lines in 2 files now uncovered.

31809 of 48744 relevant lines covered (65.26%)

1.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.05
/backend/pkg/accesslog/middleware.go
1
// Copyright 2024 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package accesslog
15

16
import (
17
        "context"
18
        "errors"
19
        "fmt"
20
        "net"
21
        "net/http"
22
        "os"
23
        "path"
24
        "runtime"
25
        "strconv"
26
        "strings"
27
        "time"
28

29
        "github.com/ant0ine/go-json-rest/rest"
30
        "github.com/sirupsen/logrus"
31

32
        "github.com/mendersoftware/mender-server/pkg/netutils"
33
        "github.com/mendersoftware/mender-server/pkg/requestlog"
34
)
35

36
const (
37
        StatusClientClosedConnection = 499
38

39
        // nolint:lll
40
        DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
41
        SimpleLogFormat  = "%s %Dμs %r %u %{User-Agent}i"
42

43
        envProxyDepth = "ACCESSLOG_PROXY_DEPTH"
44
)
45

46
// AccesLogMiddleware uses logger from requestlog and adds a fixed set
47
// of fields to every accesslog records.
48
type AccessLogMiddleware struct {
49
        // Format is not used but kept for historical use.
50
        // FIXME(QA-673): Remove unused attributes and properties from package.
51
        Format AccessLogFormat // nolint:unused
52

53
        ClientIPHook func(req *http.Request) net.IP
54
        DisableLog   func(statusCode int, r *rest.Request) bool
55

56
        recorder *rest.RecorderMiddleware
57
}
58

59
func getClientIPFromEnv() func(r *http.Request) net.IP {
8✔
60
        if proxyDepthEnv, ok := os.LookupEnv(envProxyDepth); ok {
8✔
61
                proxyDepth, err := strconv.ParseUint(proxyDepthEnv, 10, 8)
×
62
                if err == nil {
×
63
                        return func(r *http.Request) net.IP {
×
64
                                return netutils.GetIPFromXFFDepth(r, int(proxyDepth))
×
65
                        }
×
66
                }
67
        }
68
        return nil
8✔
69
}
70

71
const MaxTraceback = 32
72

73
func collectTrace() string {
×
74
        var (
×
75
                trace     [MaxTraceback]uintptr
×
76
                traceback strings.Builder
×
77
        )
×
78
        // Skip 4
×
79
        // = accesslog.LogFunc
×
80
        // + accesslog.collectTrace
×
81
        // + runtime.Callers
×
82
        // + runtime.gopanic
×
83
        n := runtime.Callers(4, trace[:])
×
84
        frames := runtime.CallersFrames(trace[:n])
×
85
        for frame, more := frames.Next(); frame.PC != 0 &&
×
86
                n >= 0; frame, more = frames.Next() {
×
87
                funcName := frame.Function
×
88
                if funcName == "" {
×
89
                        fmt.Fprint(&traceback, "???\n")
×
90
                } else {
×
91
                        fmt.Fprintf(&traceback, "%s@%s:%d",
×
92
                                frame.Function,
×
93
                                path.Base(frame.File),
×
94
                                frame.Line,
×
95
                        )
×
96
                }
×
97
                if more {
×
98
                        fmt.Fprintln(&traceback)
×
99
                }
×
100
                n--
×
101
        }
102
        return traceback.String()
×
103
}
104

105
func (mw *AccessLogMiddleware) LogFunc(
106
        ctx context.Context, startTime time.Time,
107
        w rest.ResponseWriter, r *rest.Request) {
5✔
108
        fields := logrus.Fields{
5✔
109
                "type": r.Proto,
5✔
110
                "ts": startTime.
5✔
111
                        Truncate(time.Millisecond).
5✔
112
                        Format(time.RFC3339Nano),
5✔
113
                "method":    r.Method,
5✔
114
                "path":      r.URL.Path,
5✔
115
                "useragent": r.UserAgent(),
5✔
116
                "qs":        r.URL.RawQuery,
5✔
117
        }
5✔
118
        if mw.ClientIPHook != nil {
5✔
119
                fields["clientip"] = mw.ClientIPHook(r.Request)
×
120
        }
×
121
        lc := fromContext(ctx)
5✔
122
        if lc != nil {
10✔
123
                lc.addFields(fields)
5✔
124
        }
5✔
125
        statusCode, _ := r.Env["STATUS_CODE"].(int)
5✔
126
        select {
5✔
127
        case <-ctx.Done():
×
128
                if errors.Is(ctx.Err(), context.Canceled) {
×
129
                        statusCode = StatusClientClosedConnection
×
130
                }
×
131
        default:
5✔
132
        }
133

134
        if panic := recover(); panic != nil {
5✔
135
                trace := collectTrace()
×
136
                fields["panic"] = panic
×
137
                fields["trace"] = trace
×
138
                // Wrap in recorder middleware to make sure the response is recorded
×
139
                mw.recorder.MiddlewareFunc(func(w rest.ResponseWriter, r *rest.Request) {
×
140
                        rest.Error(w, "Internal Server Error", http.StatusInternalServerError)
×
141
                })(w, r)
×
142
                statusCode = http.StatusInternalServerError
×
143
        } else if mw.DisableLog != nil && mw.DisableLog(statusCode, r) {
7✔
144
                return
2✔
145
        }
2✔
146
        rspTime := time.Since(startTime)
5✔
147
        // We do not need more than 3 digit fraction
5✔
148
        if rspTime > time.Second {
5✔
UNCOV
149
                rspTime = rspTime.Round(time.Millisecond)
×
150
        } else if rspTime > time.Millisecond {
10✔
151
                rspTime = rspTime.Round(time.Microsecond)
5✔
152
        }
5✔
153
        fields["responsetime"] = rspTime.String()
5✔
154
        fields["byteswritten"], _ = r.Env["BYTES_WRITTEN"].(int64)
5✔
155
        fields["status"] = statusCode
5✔
156

5✔
157
        logger := requestlog.GetRequestLogger(r)
5✔
158
        var level logrus.Level = logrus.InfoLevel
5✔
159
        if statusCode >= 500 {
7✔
160
                level = logrus.ErrorLevel
2✔
161
        } else if statusCode >= 300 {
12✔
162
                level = logrus.WarnLevel
5✔
163
        }
5✔
164
        logger.WithFields(fields).
5✔
165
                Log(level)
5✔
166
}
167

168
// MiddlewareFunc makes AccessLogMiddleware implement the Middleware interface.
169
func (mw *AccessLogMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
5✔
170
        if mw.ClientIPHook == nil {
10✔
171
                // If not set, try get it from env
5✔
172
                mw.ClientIPHook = getClientIPFromEnv()
5✔
173
        }
5✔
174

175
        // This middleware depends on RecorderMiddleware to work
176
        mw.recorder = new(rest.RecorderMiddleware)
5✔
177
        return func(w rest.ResponseWriter, r *rest.Request) {
10✔
178
                ctx := r.Request.Context()
5✔
179
                startTime := time.Now()
5✔
180
                ctx = withContext(ctx, &logContext{maxErrors: DefaultMaxErrors})
5✔
181
                r.Request = r.Request.WithContext(ctx)
5✔
182
                defer mw.LogFunc(ctx, startTime, w, r)
5✔
183
                // call the handler inside recorder context
5✔
184
                mw.recorder.MiddlewareFunc(h)(w, r)
5✔
185
        }
5✔
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc