Browse Source

Rework authentication

ext.rascm 4 years ago
parent
commit
8e371a0856

+ 1 - 1
build.gradle

@@ -15,7 +15,7 @@ dependencies {
15
     compile fileTree(dir: 'libs', include: '*.jar')
15
     compile fileTree(dir: 'libs', include: '*.jar')
16
     compile 'io.github.openfeign:feign-core:11.1'
16
     compile 'io.github.openfeign:feign-core:11.1'
17
     compile 'io.github.openfeign:feign-httpclient:11.1'
17
     compile 'io.github.openfeign:feign-httpclient:11.1'
18
-    compile 'io.github.openfeign:feign-jackson-jaxb:11.1'
18
+    compile 'io.github.openfeign:feign-jackson:11.1'
19
 
19
 
20
     compileOnly 'org.projectlombok:lombok:1.18.20'
20
     compileOnly 'org.projectlombok:lombok:1.18.20'
21
     annotationProcessor 'org.projectlombok:lombok:1.18.20'
21
     annotationProcessor 'org.projectlombok:lombok:1.18.20'

+ 1 - 2
settings.gradle

@@ -1,2 +1 @@
1
-rootProject.name = 'AcproTasks'
2
-
1
+rootProject.name = 'AcproTasks'

+ 33 - 23
src/main/java/com/armstrongconsulting/acprotasks/AcproRepository.java

@@ -1,11 +1,12 @@
1
 package com.armstrongconsulting.acprotasks;
1
 package com.armstrongconsulting.acprotasks;
2
 
2
 
3
+import org.apache.commons.lang.StringUtils;
3
 import org.jetbrains.annotations.NotNull;
4
 import org.jetbrains.annotations.NotNull;
4
 import org.jetbrains.annotations.Nullable;
5
 import org.jetbrains.annotations.Nullable;
5
-import org.slf4j.Logger;
6
-import org.slf4j.LoggerFactory;
7
 
6
 
8
 import com.armstrongconsulting.acprotasks.client.AcproConnector;
7
 import com.armstrongconsulting.acprotasks.client.AcproConnector;
8
+import com.armstrongconsulting.acprotasks.client.model.ItemsContainer;
9
+import com.intellij.ide.util.PropertiesComponent;
9
 import com.intellij.openapi.progress.ProgressIndicator;
10
 import com.intellij.openapi.progress.ProgressIndicator;
10
 import com.intellij.tasks.Task;
11
 import com.intellij.tasks.Task;
11
 import com.intellij.tasks.TaskRepositoryType;
12
 import com.intellij.tasks.TaskRepositoryType;
@@ -13,14 +14,12 @@ import com.intellij.tasks.impl.BaseRepository;
13
 import com.intellij.util.xmlb.annotations.Tag;
14
 import com.intellij.util.xmlb.annotations.Tag;
14
 
15
 
15
 import feign.FeignException;
16
 import feign.FeignException;
17
+import lombok.extern.slf4j.Slf4j;
16
 
18
 
17
 @Tag(AcproRepositoryType.NAME)
19
 @Tag(AcproRepositoryType.NAME)
20
+@Slf4j
18
 public class AcproRepository extends BaseRepository
21
 public class AcproRepository extends BaseRepository
19
 {
22
 {
20
-    private final Logger logger = LoggerFactory.getLogger(AcproRepository.class);
21
-
22
-    private String apiKey = null;
23
-
24
     public AcproRepository(TaskRepositoryType type)
23
     public AcproRepository(TaskRepositoryType type)
25
     {
24
     {
26
         super(type);
25
         super(type);
@@ -29,7 +28,8 @@ public class AcproRepository extends BaseRepository
29
     public AcproRepository(AcproRepository other)
28
     public AcproRepository(AcproRepository other)
30
     {
29
     {
31
         super(other);
30
         super(other);
32
-        this.apiKey = other.apiKey;
31
+        setRepositoryType(other.getRepositoryType());
32
+        setApiKey(other.getApiKey());
33
     }
33
     }
34
 
34
 
35
     public AcproRepository()
35
     public AcproRepository()
@@ -52,14 +52,22 @@ public class AcproRepository extends BaseRepository
52
     public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed,
52
     public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed,
53
         @NotNull ProgressIndicator cancelled) throws Exception
53
         @NotNull ProgressIndicator cancelled) throws Exception
54
     {
54
     {
55
-        if (apiKey == null)
55
+        if (StringUtils.isBlank(query))
56
+        {
57
+            return new Task[0];
58
+        }
59
+
60
+        ItemsContainer response = AcproConnector.getClient(this).getItems(query, offset, limit);
61
+        Task[] result = new Task[0];
62
+        if (response.getItems() != null && response.getItems().getItem() != null)
56
         {
63
         {
57
-            apiKey = AcproConnector.getClient(getUrl()).getApiKey(getUsername(), getPassword(), 28800);
64
+            result = response.getItems().getItem().stream().map(AcproTask::new).toArray(Task[]::new);
58
         }
65
         }
59
-        return new Task[0];/*AcproConnector.getClient(getUrl()).getItems(query, offset, limit, apiKey).stream().peek(a -> logger.warn(a.toString())).map(AcproTask::new)
60
-            .toArray(AcproTask[]::new);*/
66
+        return result;
61
     }
67
     }
62
 
68
 
69
+    private final AcproRepository self = this;
70
+
63
     @Override
71
     @Override
64
     public @Nullable CancellableConnection createCancellableConnection()
72
     public @Nullable CancellableConnection createCancellableConnection()
65
     {
73
     {
@@ -68,22 +76,20 @@ public class AcproRepository extends BaseRepository
68
             @Override
76
             @Override
69
             protected void doTest() throws Exception
77
             protected void doTest() throws Exception
70
             {
78
             {
79
+                // do login and set apiKey
71
                 try
80
                 try
72
                 {
81
                 {
73
-                    if (apiKey != null)
74
-                    {
75
-                        return;
76
-                    }
77
-
78
-                    apiKey = AcproConnector.getClient(getUrl()).getApiKey(getUsername(), getPassword(), 28800);
82
+                    setApiKey(
83
+                        AcproConnector.getClient(self).getApiKey(getUsername(), getPassword()));
84
+                    log.warn("Fetched api key: {}", getApiKey());
79
                 }
85
                 }
80
                 catch (FeignException.Unauthorized e)
86
                 catch (FeignException.Unauthorized e)
81
                 {
87
                 {
82
-                    throw new RuntimeException("Wrong username or password");
88
+                    throw new RuntimeException("Failed to authenticate", e);
83
                 }
89
                 }
84
-                catch (FeignException.FeignClientException e)
90
+                catch (FeignException e)
85
                 {
91
                 {
86
-                    throw new RuntimeException("Connection failed");
92
+                    throw new RuntimeException("Failed to connect to server", e);
87
                 }
93
                 }
88
             }
94
             }
89
 
95
 
@@ -95,9 +101,13 @@ public class AcproRepository extends BaseRepository
95
         };
101
         };
96
     }
102
     }
97
 
103
 
98
-    @Override
99
-    public void initializeRepository()
104
+    public String getApiKey()
105
+    {
106
+        return PropertiesComponent.getInstance().getValue("api_key");
107
+    }
108
+
109
+    public void setApiKey(String apiKey)
100
     {
110
     {
101
-        logger.info("Initialize repository");
111
+        PropertiesComponent.getInstance().setValue("api_key", apiKey);
102
     }
112
     }
103
 }
113
 }

+ 6 - 7
src/main/java/com/armstrongconsulting/acprotasks/client/AcproClient.java

@@ -1,6 +1,6 @@
1
 package com.armstrongconsulting.acprotasks.client;
1
 package com.armstrongconsulting.acprotasks.client;
2
 
2
 
3
-import com.armstrongconsulting.acprotasks.client.model.Items;
3
+import com.armstrongconsulting.acprotasks.client.model.ItemsContainer;
4
 
4
 
5
 import feign.Headers;
5
 import feign.Headers;
6
 import feign.Param;
6
 import feign.Param;
@@ -9,11 +9,10 @@ import feign.RequestLine;
9
 @Headers("Accept: application/json")
9
 @Headers("Accept: application/json")
10
 public interface AcproClient
10
 public interface AcproClient
11
 {
11
 {
12
-    @RequestLine("GET /api_key?user={user}&password={password}&expires_after_seconds={expires_after_seconds}")
13
-    String getApiKey(@Param("user") String user, @Param("password") String password,
14
-        @Param("expires_after_seconds") Integer timeToLive);
12
+    @RequestLine("GET /api_key?user={user}&password={password}")
13
+    String getApiKey(@Param("user") String user, @Param("password") String password);
15
 
14
 
16
-    @RequestLine("GET /items?q=title:{title}&start={offset}&count={limit}&api_key={apiKey}")
17
-    Items getItems(@Param("title") String titleContains, @Param("offset") Integer offset,
18
-        @Param("limit") Integer limit, @Param("apiKey") String apiKey);
15
+    @RequestLine("GET /items?q=title:{title}&start={offset}&count={limit}")
16
+    ItemsContainer getItems(@Param("title") String titleContains, @Param("offset") Integer offset,
17
+        @Param("limit") Integer limit);
19
 }
18
 }

+ 107 - 9
src/main/java/com/armstrongconsulting/acprotasks/client/AcproConnector.java

@@ -1,30 +1,128 @@
1
 package com.armstrongconsulting.acprotasks.client;
1
 package com.armstrongconsulting.acprotasks.client;
2
 
2
 
3
-import com.sun.istack.NotNull;
3
+import java.nio.charset.StandardCharsets;
4
+import java.util.Date;
5
+
6
+import org.apache.commons.io.IOUtils;
7
+import org.apache.commons.lang.StringUtils;
8
+import org.jetbrains.annotations.NotNull;
9
+
10
+import com.armstrongconsulting.acprotasks.AcproRepository;
4
 
11
 
5
 import feign.Feign;
12
 import feign.Feign;
6
 import feign.Logger;
13
 import feign.Logger;
14
+import feign.Response;
15
+import feign.RetryableException;
16
+import feign.Retryer;
17
+import feign.codec.ErrorDecoder;
7
 import feign.httpclient.ApacheHttpClient;
18
 import feign.httpclient.ApacheHttpClient;
8
-import feign.jackson.jaxb.JacksonJaxbJsonDecoder;
9
-import feign.jackson.jaxb.JacksonJaxbJsonEncoder;
19
+import feign.jackson.JacksonDecoder;
20
+import feign.jackson.JacksonEncoder;
21
+import lombok.extern.slf4j.Slf4j;
10
 
22
 
11
 public class AcproConnector
23
 public class AcproConnector
12
 {
24
 {
13
     private static AcproClient client;
25
     private static AcproClient client;
14
 
26
 
15
-    public static AcproClient getClient(@NotNull String url)
27
+    public static AcproClient getClient(@NotNull AcproRepository repository)
16
     {
28
     {
17
         if (client != null)
29
         if (client != null)
18
         {
30
         {
19
             return client;
31
             return client;
20
         }
32
         }
21
 
33
 
22
-        return Feign.builder()
34
+        client = Feign.builder()
23
             .client(new ApacheHttpClient())
35
             .client(new ApacheHttpClient())
24
-            .encoder(new JacksonJaxbJsonEncoder())
25
-            .decoder(new JacksonJaxbJsonDecoder())
26
-            .logger(new Logger.JavaLogger(AcproClient.class))
36
+            .encoder(new JacksonEncoder())
37
+            .decoder((response, type) -> {
38
+                if (type.equals(String.class))
39
+                {
40
+                    return IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8.name());
41
+                }
42
+                else
43
+                {
44
+                    return new JacksonDecoder().decode(response, type);
45
+                }
46
+            })
47
+            .logger(new Logger()
48
+            {
49
+                @Override
50
+                protected void log(String configKey, String format, Object... args)
51
+                {
52
+                    System.out.println(String.format(format, args));
53
+                }
54
+            })
27
             .logLevel(Logger.Level.FULL)
55
             .logLevel(Logger.Level.FULL)
28
-            .target(AcproClient.class, url);
56
+            .requestInterceptor(
57
+                requestTemplate -> {
58
+                    if (!StringUtils.isBlank(repository.getApiKey()))
59
+                    {
60
+                        requestTemplate.query("api_key", repository.getApiKey());
61
+                    }
62
+                })
63
+            .errorDecoder(new CustomErrorDecoder())
64
+            .retryer(new ReauthenticateRetryer(repository))
65
+            .target(AcproClient.class, repository.getUrl());
66
+
67
+        return client;
68
+    }
69
+
70
+    private AcproConnector()
71
+    {
72
+    }
73
+
74
+    @Slf4j
75
+    private static class CustomErrorDecoder implements ErrorDecoder
76
+    {
77
+        @Override
78
+        public Exception decode(String methodKey, Response response)
79
+        {
80
+            if (response.status() == 401 && !response.request().url().contains("/api_key"))
81
+            {
82
+                log.warn("Login expired. Throwing retryable exception.");
83
+                throw new RetryableException(response.status(), response.reason(), response.request().httpMethod(),
84
+                    new Date(), response.request());
85
+            }
86
+            return new ErrorDecoder.Default().decode(methodKey, response);
87
+        }
88
+    }
89
+
90
+    @Slf4j
91
+    private static class ReauthenticateRetryer implements Retryer
92
+    {
93
+        private final AcproRepository repository;
94
+        private boolean retried;
95
+
96
+        ReauthenticateRetryer(AcproRepository repository)
97
+        {
98
+            this.repository = repository;
99
+            this.retried = false;
100
+        }
101
+
102
+        ReauthenticateRetryer(ReauthenticateRetryer other)
103
+        {
104
+            this.repository = other.repository;
105
+            this.retried = other.retried;
106
+        }
107
+
108
+        @Override
109
+        public void continueOrPropagate(RetryableException e)
110
+        {
111
+            log.warn("Retrying");
112
+
113
+            if (retried)
114
+            {
115
+                throw e;
116
+            }
117
+
118
+            repository.setApiKey(client.getApiKey(repository.getUsername(), repository.getPassword()));
119
+            this.retried = true;
120
+        }
121
+
122
+        @Override
123
+        public Retryer clone()
124
+        {
125
+            return new ReauthenticateRetryer(this);
126
+        }
29
     }
127
     }
30
 }
128
 }

+ 4 - 1
src/main/java/com/armstrongconsulting/acprotasks/client/model/Item.java

@@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
4
 import com.fasterxml.jackson.annotation.JsonProperty;
4
 import com.fasterxml.jackson.annotation.JsonProperty;
5
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
5
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
6
 
6
 
7
+import lombok.ToString;
8
+
7
 @JsonInclude(JsonInclude.Include.NON_NULL)
9
 @JsonInclude(JsonInclude.Include.NON_NULL)
8
 @JsonPropertyOrder({
10
 @JsonPropertyOrder({
9
     "actualHours",
11
     "actualHours",
@@ -26,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
26
     "unreadByAssignee",
28
     "unreadByAssignee",
27
     "urgency"
29
     "urgency"
28
 })
30
 })
31
+@ToString
29
 public class Item
32
 public class Item
30
 {
33
 {
31
     @JsonProperty("actualHours")
34
     @JsonProperty("actualHours")
@@ -294,4 +297,4 @@ public class Item
294
     {
297
     {
295
         this.urgency = urgency;
298
         this.urgency = urgency;
296
     }
299
     }
297
-}
300
+}

+ 3 - 0
src/main/java/com/armstrongconsulting/acprotasks/client/model/Items.java

@@ -6,6 +6,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
6
 import com.fasterxml.jackson.annotation.JsonProperty;
6
 import com.fasterxml.jackson.annotation.JsonProperty;
7
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
7
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
8
 
8
 
9
+import lombok.ToString;
10
+
9
 @JsonInclude(JsonInclude.Include.NON_NULL)
11
 @JsonInclude(JsonInclude.Include.NON_NULL)
10
 @JsonPropertyOrder({
12
 @JsonPropertyOrder({
11
     "@rows",
13
     "@rows",
@@ -14,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
14
     "@totalNumRows",
16
     "@totalNumRows",
15
     "item"
17
     "item"
16
 })
18
 })
19
+@ToString
17
 public class Items
20
 public class Items
18
 {
21
 {
19
     @JsonProperty("@rows")
22
     @JsonProperty("@rows")

+ 30 - 0
src/main/java/com/armstrongconsulting/acprotasks/client/model/ItemsContainer.java

@@ -0,0 +1,30 @@
1
+package com.armstrongconsulting.acprotasks.client.model;
2
+
3
+import com.fasterxml.jackson.annotation.JsonInclude;
4
+import com.fasterxml.jackson.annotation.JsonProperty;
5
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
6
+
7
+import lombok.ToString;
8
+
9
+@JsonInclude(JsonInclude.Include.NON_NULL)
10
+@JsonPropertyOrder({
11
+    "items"
12
+})
13
+@ToString
14
+public class ItemsContainer
15
+{
16
+    @JsonProperty("items")
17
+    private Items items;
18
+
19
+    @JsonProperty("items")
20
+    public Items getItems()
21
+    {
22
+        return items;
23
+    }
24
+
25
+    @JsonProperty("items")
26
+    public void setItems(Items items)
27
+    {
28
+        this.items = items;
29
+    }
30
+}