Subversion Repositories SmartDukaan

Rev

Rev 36510 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 36510 Rev 36819
Line 1... Line 1...
1
package com.spice.profitmandi.web.interceptor;
1
package com.spice.profitmandi.web.interceptor;
2
 
2
 
3
import com.spice.profitmandi.web.filter.RequestCachingFilter;
3
import com.spice.profitmandi.web.filter.RequestCachingFilter;
-
 
4
import com.spice.profitmandi.web.model.LoginDetails;
-
 
5
import com.spice.profitmandi.web.util.CookiesProcessor;
4
import org.apache.logging.log4j.LogManager;
6
import org.apache.logging.log4j.LogManager;
5
import org.apache.logging.log4j.Logger;
7
import org.apache.logging.log4j.Logger;
6
import org.springframework.beans.factory.annotation.Autowired;
8
import org.springframework.beans.factory.annotation.Autowired;
7
import org.springframework.data.redis.core.RedisTemplate;
9
import org.springframework.data.redis.core.RedisTemplate;
8
import org.springframework.stereotype.Component;
10
import org.springframework.stereotype.Component;
Line 23... Line 25...
23
    private static final String REQUEST_ATTR_IDEM_KEY = "postInterceptor.idemKey";
25
    private static final String REQUEST_ATTR_IDEM_KEY = "postInterceptor.idemKey";
24
 
26
 
25
    @Autowired
27
    @Autowired
26
    private RedisTemplate<String, Object> redisTemplate;
28
    private RedisTemplate<String, Object> redisTemplate;
27
 
29
 
-
 
30
    @Autowired
-
 
31
    private CookiesProcessor cookiesProcessor;
-
 
32
 
28
    @Override
33
    @Override
29
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
34
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
30
        String idempotencyHeader = request.getHeader("IdempotencyKey");
35
        // Idempotency applies only to state-changing methods. GET/HEAD/OPTIONS are
31
        boolean isPost = "POST".equalsIgnoreCase(request.getMethod());
36
        // safe/repeatable and must never be deduped (a stale read must not be 400'd).
32
 
-
 
33
        if (!isPost && (idempotencyHeader == null || idempotencyHeader.isEmpty())) {
37
        if (!isMutatingMethod(request.getMethod())) {
34
            return true;
38
            return true;
35
        }
39
        }
36
 
40
 
37
        String idemKey = buildIdempotencyKey(request, idempotencyHeader);
41
        String idemKey = buildIdempotencyKey(request);
38
        if (idemKey == null) {
42
        if (idemKey == null) {
-
 
43
            // Nothing reliable to key on (e.g. multipart upload with no client key).
39
            return true;
44
            return true;
40
        }
45
        }
41
 
46
 
42
        String redisKey = IDEM_PREFIX + idemKey;
47
        String redisKey = IDEM_PREFIX + idemKey;
43
        Boolean claimed = redisTemplate.opsForValue()
48
        Boolean claimed = redisTemplate.opsForValue()
Line 72... Line 77...
72
        } else {
77
        } else {
73
            redisTemplate.delete(redisKey);
78
            redisTemplate.delete(redisKey);
74
        }
79
        }
75
    }
80
    }
76
 
81
 
77
    private String buildIdempotencyKey(HttpServletRequest request, String header) {
82
    private boolean isMutatingMethod(String method) {
78
        if (header != null && !header.isEmpty()) {
83
        return "POST".equalsIgnoreCase(method)
79
            String uri = request.getRequestURI();
84
                || "PUT".equalsIgnoreCase(method)
-
 
85
                || "PATCH".equalsIgnoreCase(method)
80
            String query = request.getQueryString();
86
                || "DELETE".equalsIgnoreCase(method);
-
 
87
    }
-
 
88
 
-
 
89
    /**
-
 
90
     * Server-authoritative idempotency key. Dedupes on the request CONTENT
81
            String scope = header + "|" + uri + (query != null ? "?" + query : "");
91
     * (a hash of the body) scoped to the authenticated partner + method + uri, so
82
            return sha256(scope.getBytes(java.nio.charset.StandardCharsets.UTF_8));
92
     * that a client which rotates or otherwise mismanages its IdempotencyKey
-
 
93
     * header (the jQuery SPA, or an old mobile build that cannot be force-updated)
-
 
94
     * can no longer produce distinct keys for identical submissions. The client
-
 
95
     * header is used only as a fallback when the body is not cached (multipart /
-
 
96
     * form-encoded requests).
83
        }
97
     */
84
        if ("POST".equalsIgnoreCase(request.getMethod())
98
    private String buildIdempotencyKey(HttpServletRequest request) {
-
 
99
        String base = null;
85
                && request instanceof RequestCachingFilter.CachedBodyRequest) {
100
        if (request instanceof RequestCachingFilter.CachedBodyRequest) {
86
            byte[] body = ((RequestCachingFilter.CachedBodyRequest) request).getCachedBody();
101
            byte[] body = ((RequestCachingFilter.CachedBodyRequest) request).getCachedBody();
87
            if (body.length > 0) {
102
            if (body != null && body.length > 0) {
88
                return sha256(body);
103
                base = "body:" + sha256(body);
-
 
104
            }
-
 
105
        }
-
 
106
        if (base == null) {
-
 
107
            String header = request.getHeader("IdempotencyKey");
-
 
108
            if (header != null && !header.isEmpty()) {
-
 
109
                base = "hdr:" + header;
-
 
110
            }
-
 
111
        }
-
 
112
        if (base == null) {
-
 
113
            return null;
-
 
114
        }
-
 
115
 
-
 
116
        String principal = resolvePrincipal(request);
-
 
117
        String query = request.getQueryString();
-
 
118
        String scope = principal + "|" + request.getMethod() + "|" + request.getRequestURI()
-
 
119
                + (query != null ? "?" + query : "") + "|" + base;
-
 
120
        return sha256(scope.getBytes(java.nio.charset.StandardCharsets.UTF_8));
-
 
121
    }
-
 
122
 
-
 
123
    /**
-
 
124
     * Scope the key to the logged-in partner so two different partners submitting
-
 
125
     * an identical payload are not collapsed into a single claim. Falls back to
-
 
126
     * "anon" for unauthenticated / public mutating endpoints.
-
 
127
     */
-
 
128
    private String resolvePrincipal(HttpServletRequest request) {
-
 
129
        try {
-
 
130
            LoginDetails login = cookiesProcessor.getCookiesObject(request);
-
 
131
            if (login != null && login.getFofoId() > 0) {
-
 
132
                return "fofo:" + login.getFofoId();
89
            }
133
            }
-
 
134
        } catch (Exception ignore) {
-
 
135
            // session/cookies absent on public endpoints -> fall through to anon
90
        }
136
        }
91
        return null;
137
        return "anon";
92
    }
138
    }
93
 
139
 
94
    private static String sha256(byte[] data) {
140
    private static String sha256(byte[] data) {
95
        try {
141
        try {
96
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
142
            MessageDigest digest = MessageDigest.getInstance("SHA-256");