13 | * The principal and credentials should be set with an
14 | * Object that provides the respective property via its
15 | * Object.toString() method. The simplest such Object to use is
16 | * String.
17 | *
18 | * @author Ben Alex
19 | */
20 | public class ConnectionAuthenticationToken extends AbstractAuthenticationToken {
21 |
22 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
23 |
24 | // ~ Instance fields
25 | // ================================================================================================
26 |
27 | private final Object principal;
28 | private Object credentials;
29 | private Object hostname;
30 | private Object port;
31 |
32 | // ~ Constructors
33 | // ===================================================================================================
34 |
35 | /**
36 | * This constructor can be safely used by any code that wishes to create a
37 | * ConnectionAuthenticationToken, as the {@link #isAuthenticated()}
38 | * will return false.
39 | *
40 | */
41 | public ConnectionAuthenticationToken(Object principal, Object credentials, Object hostname, Object port) {
42 | super(null);
43 | this.principal = principal;
44 | this.credentials = credentials;
45 | this.hostname = hostname;
46 | this.port = port;
47 | setAuthenticated(false);
48 | }
49 |
50 | /**
51 | * This constructor should only be used by AuthenticationManager or
52 | * AuthenticationProvider implementations that are satisfied with
53 | * producing a trusted (i.e. {@link #isAuthenticated()} = true)
54 | * authentication token.
55 | *
56 | * @param principal
57 | * @param credentials
58 | * @param authorities
59 | */
60 | public ConnectionAuthenticationToken(Object principal, Object credentials, Object hostname, Object port,
61 | Collection extends GrantedAuthority> authorities) {
62 | super(authorities);
63 | this.principal = principal;
64 | this.credentials = credentials;
65 | this.hostname = hostname;
66 | this.port = port;
67 | super.setAuthenticated(true); // must use super, as we override
68 | }
69 |
70 | // ~ Methods
71 | // ========================================================================================================
72 |
73 | public Object getCredentials() {
74 | return this.credentials;
75 | }
76 |
77 | public Object getPrincipal() {
78 | return this.principal;
79 | }
80 |
81 | public Object getHostname() {
82 | return this.hostname;
83 | }
84 |
85 | public Object getPort() { return this.port; }
86 |
87 | public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
88 | if (isAuthenticated) {
89 | throw new IllegalArgumentException(
90 | "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
91 | }
92 |
93 | super.setAuthenticated(false);
94 | }
95 |
96 | @Override
97 | public void eraseCredentials() {
98 | super.eraseCredentials();
99 | credentials = null;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/ui/app/grid/grid.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OnInit,
3 | Component,
4 | ElementRef,
5 | HostListener,
6 | OnDestroy,
7 | Renderer,
8 | } from '@angular/core';
9 |
10 | @Component({
11 | selector: '[gm-grid]',
12 | template: '',
13 | styleUrls: ['./grid.component.scss']
14 | })
15 | export class GridManiaComponent {}
16 |
17 | @Component({
18 | selector: 'gm-divider,[gm-divider]',
19 | template: ''
20 | })
21 | export class DividerComponent implements OnDestroy, OnInit {
22 | protected resizing: boolean = false;
23 | protected documentColumnResizeListener: any;
24 | protected documentColumnResizeEndListener: any;
25 | protected draggerPosX: number;
26 | protected draggerPosY: number;
27 | protected draggerWidth: number;
28 | protected draggerHeight: number;
29 | protected previousCursor: any;
30 | protected horizontal: boolean = false;
31 |
32 | protected container: any;
33 | protected prev: any;
34 | protected next: any;
35 |
36 | @HostListener('mousedown', ['$event']) onMousedown(event: MouseEvent) {
37 | if (event.target === this.container && event.button === 0 && !event.ctrlKey) {
38 | if (this.resizing) {
39 | this.onMouseUp(event);
40 | return;
41 | }
42 |
43 | this.resizing = true;
44 |
45 | this.draggerWidth = this.container.offsetWidth;
46 | this.draggerHeight = this.container.offsetHeight;
47 | this.draggerPosX = this.container.getBoundingClientRect().left + document.body.scrollLeft + this.draggerWidth - event.pageX;
48 | this.draggerPosY = this.container.getBoundingClientRect().top + document.body.scrollTop + this.draggerHeight - event.pageY;
49 | this.previousCursor = document.body.style['cursor'];
50 | document.body.style['cursor'] = this.horizontal ? 'col-resize' : 'row-resize';
51 | }
52 | }
53 |
54 | constructor(protected el: ElementRef, protected renderer: Renderer) {}
55 |
56 | ngOnInit() {
57 | this.container = this.el.nativeElement;
58 | this.prev = this.container.previousElementSibling;
59 | this.next = this.container.nextElementSibling;
60 | this.horizontal = this.prev.hasAttribute('gm-col');
61 |
62 | this.documentColumnResizeListener = this.renderer.listenGlobal('body', 'mousemove', (event) => {
63 | if (this.resizing) {
64 | if (this.horizontal) {
65 | this.onHorizontalResize(event);
66 | } else {
67 | this.onVerticalResize(event);
68 | }
69 | }
70 | });
71 |
72 | this.documentColumnResizeEndListener = this.renderer.listenGlobal('body', 'mouseup', this.onMouseUp);
73 | }
74 |
75 | onMouseUp = (event) => {
76 | if (this.resizing) {
77 | this.resizing = false;
78 | document.body.style['cursor'] = this.previousCursor;
79 | }
80 | }
81 |
82 | onHorizontalResize(event) {
83 | const totalWidth = this.prev.offsetWidth + this.next.offsetWidth;
84 |
85 | let leftPercentage = (
86 | (
87 | (event.pageX - this.prev.getBoundingClientRect().left + document.body.scrollLeft) +
88 | (this.draggerPosX - this.draggerWidth / 2)
89 | ) / totalWidth
90 | );
91 | let rightPercentage = 1 - leftPercentage;
92 |
93 | this.prev.style['flex'] = leftPercentage.toString();
94 | this.next.style['flex'] = rightPercentage.toString();
95 | }
96 |
97 | onVerticalResize(event) {
98 | const totalHeight = this.prev.offsetHeight + this.next.offsetHeight;
99 |
100 | let topPercentage = (
101 | (
102 | (event.pageY - this.prev.getBoundingClientRect().top + document.body.scrollTop) +
103 | (this.draggerPosY - this.draggerHeight / 2)
104 | ) / totalHeight
105 | );
106 | let bottomPercentage = 1 - topPercentage;
107 |
108 | this.prev.style['flex'] = topPercentage.toString();
109 | this.next.style['flex'] = bottomPercentage.toString();
110 | }
111 |
112 | ngOnDestroy() {
113 | if (this.documentColumnResizeListener) {
114 | this.documentColumnResizeListener();
115 | }
116 | if (this.documentColumnResizeEndListener) {
117 | this.documentColumnResizeEndListener();
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [v1.0.0-alpha.8](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.8)
4 |
5 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.7...v1.0.0-alpha.8)
6 |
7 | **Closed issues:**
8 |
9 | - Clear button collapses console window [\#26](https://github.com/paxtonhare/marklogic-debugger/issues/26)
10 | - Stack browser doesn't work correctly [\#25](https://github.com/paxtonhare/marklogic-debugger/issues/25)
11 | - Error dialog is too big [\#24](https://github.com/paxtonhare/marklogic-debugger/issues/24)
12 |
13 | ## [v1.0.0-alpha.7](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.7) (2017-02-27)
14 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.6...v1.0.0-alpha.7)
15 |
16 | **Closed issues:**
17 |
18 | - Codemirror window isn't full width [\#22](https://github.com/paxtonhare/marklogic-debugger/issues/22)
19 | - Duplicate URIs in list of modules [\#21](https://github.com/paxtonhare/marklogic-debugger/issues/21)
20 | - Document Prereqs [\#20](https://github.com/paxtonhare/marklogic-debugger/issues/20)
21 |
22 | ## [v1.0.0-alpha.6](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.6) (2017-02-14)
23 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.5...v1.0.0-alpha.6)
24 |
25 | **Closed issues:**
26 |
27 | - invoke a main module from the debugger [\#19](https://github.com/paxtonhare/marklogic-debugger/issues/19)
28 | - determine if breakpoint is valid when set [\#18](https://github.com/paxtonhare/marklogic-debugger/issues/18)
29 | - update node dependencies [\#17](https://github.com/paxtonhare/marklogic-debugger/issues/17)
30 | - Rename the "Debugging is ON|OFF" [\#16](https://github.com/paxtonhare/marklogic-debugger/issues/16)
31 | - Can we pause a long running request? [\#10](https://github.com/paxtonhare/marklogic-debugger/issues/10)
32 | - Indicate that something happened [\#3](https://github.com/paxtonhare/marklogic-debugger/issues/3)
33 |
34 | ## [v1.0.0-alpha.5](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.5) (2017-02-10)
35 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.4...v1.0.0-alpha.5)
36 |
37 | **Closed issues:**
38 |
39 | - Add scroll bar to the Console area [\#15](https://github.com/paxtonhare/marklogic-debugger/issues/15)
40 | - debug area changes height when stepping [\#14](https://github.com/paxtonhare/marklogic-debugger/issues/14)
41 | - Welcome dialog is showing many times [\#13](https://github.com/paxtonhare/marklogic-debugger/issues/13)
42 |
43 | ## [v1.0.0-alpha.4](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.4) (2017-02-10)
44 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.3...v1.0.0-alpha.4)
45 |
46 | **Closed issues:**
47 |
48 | - Doesn't work in safari. [\#12](https://github.com/paxtonhare/marklogic-debugger/issues/12)
49 | - bug in getting files from Filesystem [\#11](https://github.com/paxtonhare/marklogic-debugger/issues/11)
50 |
51 | ## [v1.0.0-alpha.3](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.3) (2017-02-09)
52 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/v1.0.0-alpha.2...v1.0.0-alpha.3)
53 |
54 | **Closed issues:**
55 |
56 | - Create a custom session name to avoid stomping on other appservers [\#9](https://github.com/paxtonhare/marklogic-debugger/issues/9)
57 | - Change to non-8080 port [\#8](https://github.com/paxtonhare/marklogic-debugger/issues/8)
58 |
59 | ## [v1.0.0-alpha.2](https://github.com/paxtonhare/marklogic-debugger/tree/v1.0.0-alpha.2) (2017-02-09)
60 | [Full Changelog](https://github.com/paxtonhare/marklogic-debugger/compare/1.0.0-alpha.1...v1.0.0-alpha.2)
61 |
62 | **Closed issues:**
63 |
64 | - add ability to toggle or remove breakpoints [\#7](https://github.com/paxtonhare/marklogic-debugger/issues/7)
65 | - README points to a different project [\#6](https://github.com/paxtonhare/marklogic-debugger/issues/6)
66 | - ability to read files off filesystem [\#5](https://github.com/paxtonhare/marklogic-debugger/issues/5)
67 | - Need a "didn't authenticate" message [\#4](https://github.com/paxtonhare/marklogic-debugger/issues/4)
68 | - fix breakpoints [\#2](https://github.com/paxtonhare/marklogic-debugger/issues/2)
69 | - Consistent highlighting [\#1](https://github.com/paxtonhare/marklogic-debugger/issues/1)
70 |
71 | ## [1.0.0-alpha.1](https://github.com/paxtonhare/marklogic-debugger/tree/1.0.0-alpha.1) (2017-02-02)
72 |
73 |
74 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
75 |
--------------------------------------------------------------------------------
/src/main/resources/modules/get-files.xqy:
--------------------------------------------------------------------------------
1 | xquery version "1.0-ml";
2 |
3 | import module namespace admin = "http://marklogic.com/xdmp/admin"
4 | at "/MarkLogic/admin.xqy";
5 |
6 | declare option xdmp:mapping "false";
7 |
8 | declare variable $serverId external;
9 |
10 | declare function local:build-files($uris as xs:string*, $parent as xs:string, $a as json:array)
11 | {
12 | let $parent :=
13 | if (fn:ends-with($parent, "/")) then $parent
14 | else
15 | $parent || "/"
16 | let $files :=
17 | fn:distinct-values(
18 | for $uri in $uris[fn:matches(., "^" || $parent || "[^/]+$")]
19 | let $file := fn:replace($uri, "^" || $parent || "([^/]+)$", "$1")
20 | return
21 | $file
22 | )
23 | for $file in $files
24 | let $o := json:object()
25 | let $_ := map:put($o, "name", $file)
26 | let $_ := map:put($o, "type", "file")
27 | let $_ := map:put($o, "collapsed", fn:true())
28 | let $_ := map:put($o, "uri", $parent || $file)
29 | return
30 | json:array-push($a, $o)
31 | };
32 |
33 | declare function local:build-dirs($uris as xs:string*, $parent as xs:string)
34 | {
35 | let $parent :=
36 | if (fn:ends-with($parent, "/")) then $parent
37 | else
38 | $parent || "/"
39 | let $dirs :=
40 | fn:distinct-values(
41 | for $uri in $uris[fn:matches(., "^" || $parent || "[^/]+/.*$")]
42 | let $dir := fn:replace($uri, "^" || $parent || "([^/]+)/.*$", "$1")
43 | return
44 | $dir
45 | )
46 | let $a := json:array()
47 | let $_ :=
48 | if ($parent eq '/') then
49 | local:build-files($uris, $parent, $a)
50 | else ()
51 | let $_ :=
52 | for $dir in $dirs
53 | let $o := json:object()
54 | let $oo := local:build-dirs($uris, $parent || $dir)
55 | let $_ := local:build-files($uris, $parent || $dir, $oo)
56 | let $_ := map:put($o, "name", $dir)
57 | let $_ := map:put($o, "uri", $parent || $dir)
58 | let $_ := map:put($o, "type", "dir")
59 | let $_ := map:put($o, "children", $oo)
60 | let $_ := map:put($o, "collapsed", fn:true())
61 | return
62 | json:array-push($a, $o)
63 | return $a
64 | };
65 |
66 | declare function local:get-system-files($root-dir, $dirs, $a as json:array) {
67 | for $entry in $dirs/dir:entry[dir:type = "file"]
68 | let $o := json:object()
69 | let $_ := map:put($o, "name", fn:string($entry/dir:filename))
70 | let $_ := map:put($o, "type", "file")
71 | let $_ := map:put($o, "collapsed", fn:true())
72 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $root-dir, ""))
73 | return
74 | json:array-push($a, $o)
75 | };
76 |
77 | declare function local:get-system-dirs($root-dir, $dirs, $a as json:array) {
78 | for $entry in $dirs/dir:entry[dir:type = "directory"]
79 | return
80 | let $o := json:object()
81 | let $children := json:array()
82 | let $child-dirs := xdmp:filesystem-directory($entry/dir:pathname)
83 | let $_ := local:get-system-dirs($root-dir, $child-dirs, $children)
84 | let $_ := local:get-system-files($root-dir, $child-dirs, $children)
85 | let $_ := map:put($o, "name", fn:string($entry/dir:filename))
86 | let $_ := map:put($o, "uri", fn:replace($entry/dir:pathname, $root-dir, ""))
87 | let $_ := map:put($o, "type", "dir")
88 | let $_ := map:put($o, "collapsed", fn:true())
89 | let $_ := map:put($o, "children", $children)
90 | return
91 | json:array-push($a, $o)
92 | };
93 |
94 | let $server-id := xs:unsignedLong($serverId)
95 | let $config := admin:get-configuration()
96 | let $modules-db := admin:appserver-get-modules-database($config, $server-id)
97 | let $server-root := admin:appserver-get-root($config, $server-id)
98 | let $obj :=
99 | if ($modules-db = 0) then
100 | let $o := json:object()
101 | let $_ := map:put($o, "name", "/")
102 | let $_ := map:put($o, "type", "dir")
103 | let $_ := map:put($o, "collapsed", fn:false())
104 | let $children := json:array()
105 | let $dirs := xdmp:filesystem-directory($server-root)
106 | let $_ := local:get-system-dirs($server-root, $dirs, $children)
107 | let $_ := map:put($o, "children", $children)
108 | return
109 | $o
110 | else
111 | let $uris :=
112 | xdmp:invoke-function(function() {
113 | for $x in cts:search(fn:doc(), cts:and-query(()), "unfiltered")
114 | let $uri := xdmp:node-uri($x)
115 | where fn:not(fn:ends-with($uri, "/"))
116 | order by $uri ascending
117 | return
118 | $uri
119 | },
120 | map:new((
121 | map:entry("isolation", "different-transaction"),
122 | map:entry("database", $modules-db),
123 | map:entry("transactionMode", "update-auto-commit")
124 | )))
125 | let $o := json:object()
126 | let $_ := map:put($o, "name", "/")
127 | let $_ := map:put($o, "type", "dir")
128 | let $_ := map:put($o, "collapsed", fn:false())
129 | let $children := local:build-dirs($uris, "/")
130 | let $_ := map:put($o, "children", $children)
131 | return
132 | $o
133 | return
134 | xdmp:to-json($obj)
135 |
--------------------------------------------------------------------------------
/src/main/java/com/marklogic/debugger/auth/MarkLogicAuthenticationManager.java:
--------------------------------------------------------------------------------
1 | package com.marklogic.debugger.auth;
2 |
3 | import com.marklogic.spring.http.RestClient;
4 | import com.marklogic.spring.http.RestConfig;
5 | import com.marklogic.spring.http.SimpleRestConfig;
6 | import org.apache.http.auth.AuthScope;
7 | import org.apache.http.auth.Credentials;
8 | import org.apache.http.auth.UsernamePasswordCredentials;
9 | import org.apache.http.client.CredentialsProvider;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.security.authentication.AuthenticationManager;
12 | import org.springframework.security.authentication.AuthenticationProvider;
13 | import org.springframework.security.authentication.BadCredentialsException;
14 | import org.springframework.security.core.Authentication;
15 | import org.springframework.security.core.AuthenticationException;
16 | import org.springframework.web.client.HttpClientErrorException;
17 |
18 | import java.net.URI;
19 |
20 | /**
21 | * Implements Spring Security's AuthenticationManager interface so that it can authenticate users by making a simple
22 | * request to MarkLogic and checking for a 401. Also implements AuthenticationProvider so that it can be used with
23 | * Spring Security's ProviderManager.
24 | */
25 | public class MarkLogicAuthenticationManager implements AuthenticationProvider, AuthenticationManager {
26 |
27 | private SimpleRestConfig restConfig;
28 |
29 | private String pathToAuthenticateAgainst = "/";
30 |
31 | /**
32 | * A RestConfig instance is needed so a request can be made to MarkLogic to see if the user can successfully
33 | * authenticate.
34 | *
35 | * @param restConfig
36 | */
37 | public MarkLogicAuthenticationManager(RestConfig restConfig) {
38 | this.restConfig = (SimpleRestConfig)restConfig;
39 | }
40 |
41 | @Override
42 | public boolean supports(Class> authentication) {
43 | return ConnectionAuthenticationToken.class.isAssignableFrom(authentication);
44 | }
45 |
46 | @Override
47 | public Authentication authenticate(Authentication authentication) throws AuthenticationException {
48 | if (!(authentication instanceof ConnectionAuthenticationToken)) {
49 | throw new IllegalArgumentException(
50 | getClass().getName() + " only supports " + ConnectionAuthenticationToken.class.getName());
51 | }
52 |
53 | ConnectionAuthenticationToken token = (ConnectionAuthenticationToken) authentication;
54 | String username = token.getPrincipal().toString();
55 | String password = token.getCredentials().toString();
56 | String hostname = token.getHostname().toString();
57 | int port = Integer.parseInt(token.getPort().toString());
58 |
59 | if (username == "" || password == "" || hostname == "" || port == 0) {
60 | throw new BadCredentialsException("Invalid credentials");
61 | }
62 | /**
63 | * For now, building a new RestTemplate each time. This should in general be okay, because we're typically not
64 | * authenticating users over and over.
65 | */
66 | restConfig.setHost(hostname);
67 | restConfig.setRestPort(port);
68 | RestClient client = new RestClient(restConfig, new SimpleCredentialsProvider(username, password));
69 | URI uri = client.buildUri(pathToAuthenticateAgainst, null);
70 | try {
71 | client.getRestOperations().getForEntity(uri, String.class);
72 | } catch (HttpClientErrorException ex) {
73 | if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) {
74 | // Authenticated, but the path wasn't found - that's okay, we just needed to verify authentication
75 | } else if (HttpStatus.UNAUTHORIZED.equals(ex.getStatusCode())) {
76 | throw new BadCredentialsException("Invalid credentials");
77 | } else {
78 | throw ex;
79 | }
80 | }
81 |
82 | return new ConnectionAuthenticationToken(token.getPrincipal(), token.getCredentials(),
83 | token.getHostname(), token.getPort(), token.getAuthorities());
84 | }
85 |
86 | public void setPathToAuthenticateAgainst(String pathToAuthenticateAgainst) {
87 | this.pathToAuthenticateAgainst = pathToAuthenticateAgainst;
88 | }
89 | }
90 |
91 | /**
92 | * Simple implementation that is good for one-time requests.
93 | */
94 | class SimpleCredentialsProvider implements CredentialsProvider {
95 |
96 | private String username;
97 | private String password;
98 |
99 | public SimpleCredentialsProvider(String username, String password) {
100 | this.username = username;
101 | this.password = password;
102 | }
103 |
104 | @Override
105 | public void setCredentials(AuthScope authscope, Credentials credentials) {
106 | }
107 |
108 | @Override
109 | public Credentials getCredentials(AuthScope authscope) {
110 | return new UsernamePasswordCredentials(username, password);
111 | }
112 |
113 | @Override
114 | public void clear() {
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/marklogic/debugger/Config.java:
--------------------------------------------------------------------------------
1 | package com.marklogic.debugger;
2 |
3 | import com.marklogic.debugger.auth.AuthSuccessHandler;
4 | import com.marklogic.debugger.auth.ConnectionAuthenticationFilter;
5 | import com.marklogic.debugger.auth.RestAuthenticationEntryPoint;
6 | import org.apache.http.client.CredentialsProvider;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
14 |
15 | import com.marklogic.spring.http.RestConfig;
16 | import com.marklogic.spring.http.SimpleRestConfig;
17 | import com.marklogic.spring.http.proxy.HttpProxy;
18 | import com.marklogic.debugger.auth.MarkLogicAuthenticationManager;
19 | import com.marklogic.spring.security.context.SpringSecurityCredentialsProvider;
20 | import com.marklogic.spring.security.web.util.matcher.CorsRequestMatcher;
21 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
22 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
23 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
24 |
25 | /**
26 | * Extends Spring Boot's default web security configuration class and hooks in MarkLogic-specific classes from
27 | * marklogic-spring-web. Feel free to customize as needed.
28 | */
29 | @Configuration
30 | @EnableWebSecurity
31 | public class Config extends WebSecurityConfigurerAdapter {
32 |
33 | /**
34 | * @return a config class with ML connection properties
35 | */
36 | @Bean
37 | public RestConfig restConfig() {
38 | return new SimpleRestConfig();
39 | }
40 |
41 | @Bean
42 | public CredentialsProvider credentialsProvider() {
43 | return new SpringSecurityCredentialsProvider();
44 | }
45 |
46 | @Autowired
47 | private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
48 |
49 | @Autowired
50 | private AuthSuccessHandler authenticationSuccessHandler;
51 |
52 | // /**
53 | // * @return an HttpProxy that a Spring MVC controller can use for proxying requests to MarkLogic. By default, uses
54 | // * Spring Security for credentials - this relies on Spring Security not erasing the user's credentials so
55 | // * that the username/password can be passed to MarkLogic on every request for authentication.
56 | // */
57 | // @Bean
58 | // public HttpProxy httpProxy() {
59 | // return new HttpProxy(restConfig(), credentialsProvider());
60 | // }
61 |
62 | /**
63 | * We seem to need this defined as a bean; otherwise, aspects of the default Spring Boot security will still remain.
64 | *
65 | * @return
66 | */
67 | @Bean
68 | public MarkLogicAuthenticationManager markLogicAuthenticationManager() {
69 | return new MarkLogicAuthenticationManager(restConfig());
70 | }
71 |
72 | /**
73 | * Sets MarkLogicAuthenticationProvider as the authentication manager, which overrides the in-memory authentication
74 | * manager that Spring Boot uses by default. We also have to set eraseCredentials to false so that the password is
75 | * kept in the Authentication object, which allows HttpProxy to use it when authenticating against MarkLogic.
76 | */
77 | @Override
78 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
79 | super.configure(auth);
80 | auth.parentAuthenticationManager(markLogicAuthenticationManager());
81 | auth.eraseCredentials(false);
82 | }
83 |
84 | /**
85 | * Configures what requests require authentication and which ones are always permitted. Uses CorsRequestMatcher to
86 | * allow for certain requests - e.g. put/post/delete requests - to be proxied successfully back to MarkLogic.
87 | *
88 | * This uses a form login by default, as for many MarkLogic apps (particularly demos), it's convenient to be able to
89 | * easily logout and login as a different user to show off security features. Spring Security has a very plain form
90 | * login page - you can customize this, just google for examples.
91 | */
92 | @Override
93 | protected void configure(HttpSecurity http) throws Exception {
94 |
95 | ConnectionAuthenticationFilter authFilter = new ConnectionAuthenticationFilter();
96 | authFilter.setAuthenticationManager(markLogicAuthenticationManager());
97 | authFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
98 | authFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
99 | http
100 | .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
101 | .csrf().disable()
102 | .exceptionHandling()
103 | .authenticationEntryPoint(restAuthenticationEntryPoint)
104 | .and()
105 | .authorizeRequests()
106 | .antMatchers(getAlwaysPermittedPatterns()).permitAll().anyRequest().authenticated();
107 | }
108 |
109 | @Bean
110 | public AuthSuccessHandler mySuccessHandler(){
111 | return new AuthSuccessHandler();
112 | }
113 |
114 | @Bean
115 | public SimpleUrlAuthenticationFailureHandler myFailureHandler(){
116 | return new SimpleUrlAuthenticationFailureHandler();
117 | }
118 |
119 | /**
120 | * Defines a set of URLs that are always permitted - these are based on the presumed contents of the
121 | * src/main/resources/static directory.
122 | *
123 | * @return
124 | */
125 | protected String[] getAlwaysPermittedPatterns() {
126 | return new String[] {
127 | "/bower_components/**",
128 | "/fonts/**",
129 | "/images/**",
130 | "/styles/**",
131 | "/*.js",
132 | "/*.ttf",
133 | "/*.woff",
134 | "/*.svg",
135 | "/*.woff2",
136 | "/*.eot",
137 | "/*.css",
138 | "/",
139 | "/index.html",
140 | "/api/server/status"
141 | };
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/ui/app/marklogic/marklogic.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Headers, Http, RequestOptionsArgs, Response } from '@angular/http';
3 | import { Breakpoint } from './breakpoint';
4 |
5 | import * as _ from 'lodash';
6 |
7 | @Injectable()
8 | export class MarkLogicService {
9 |
10 | constructor(private http: Http) {}
11 |
12 | getServers() {
13 | return this.get('/api/servers');
14 | }
15 |
16 | enableServer(serverId) {
17 | return this.http.get('/api/servers/' + serverId + '/enable');
18 | }
19 |
20 | disableServer(serverId) {
21 | return this.http.get('/api/servers/' + serverId + '/disable');
22 | }
23 |
24 | getFiles(serverId) {
25 | return this.get('/api/servers/' + serverId + '/files');
26 | }
27 |
28 | getSystemFiles() {
29 | return this.get('/api/marklogic/files');
30 | }
31 |
32 | getFile(serverId, uri) {
33 | let options: RequestOptionsArgs = {
34 | headers: new Headers({'Accept': 'text/plain'})
35 | };
36 | const url = '/api/servers/' + serverId + '/file?uri=' + uri;
37 | return this.http.get(url, options).map((resp: Response) => {
38 | return resp.text();
39 | });
40 | }
41 |
42 | getServerEnabled(serverId) {
43 | return this.get(`/api/servers/${serverId}`);
44 | }
45 |
46 | getRequests(serverId) {
47 | return this.get(`/api/servers/${serverId}/requests`);
48 | }
49 |
50 | getRequest(serverId, requestId) {
51 | return this.get(`/api/servers/${serverId}/requests/${requestId}`);
52 | }
53 |
54 | getStack(requestId) {
55 | return this.get(`/api/requests/${requestId}/stack`);
56 | }
57 |
58 | stepOver(requestId: any) {
59 | return this.http.get(`/api/requests/${requestId}/step-over`);
60 | }
61 |
62 | stepIn(requestId: any) {
63 | return this.http.get(`/api/requests/${requestId}/step-in`);
64 | }
65 |
66 | stepOut(requestId: any) {
67 | return this.http.get(`/api/requests/${requestId}/step-out`);
68 | }
69 |
70 | continue(requestId: any) {
71 | return this.http.get(`/api/requests/${requestId}/continue`);
72 | }
73 |
74 | pause(requestId: any) {
75 | return this.http.get(`/api/requests/${requestId}/pause`);
76 | }
77 |
78 | get(url: string) {
79 | return this.http.get(url).map((resp: Response) => {
80 | return resp.json();
81 | });
82 | }
83 |
84 | getTrace(id: string) {
85 | return this.http.get('/hub/traces/' + id);
86 | }
87 |
88 | getIds(query: string) {
89 | return this.http.get('/hub/traces/ids?q=' + query);
90 | }
91 |
92 | getAllBreakpoints(server: string): Map> {
93 | let map = new Map>();
94 | let parsed = JSON.parse(localStorage.getItem(`breakpoints-${server}`));
95 | if (parsed) {
96 | Object.keys(parsed).forEach((key) => {
97 | let breakpoints = new Array();
98 | for (let bp of parsed[key]) {
99 | let breakpoint = new Breakpoint(bp.uri, bp.line, bp.enabled);
100 | breakpoints.push(breakpoint);
101 | }
102 | map.set(key, breakpoints);
103 | });
104 | }
105 | return map;
106 | }
107 |
108 | getBreakpoints(server: string, uri: string): Array {
109 | let breakpoints = this.getAllBreakpoints(server);
110 | return breakpoints.get(uri) || new Array();
111 | }
112 |
113 | enableBreakpoint(server: string, uri: string, line: number) {
114 | let breakpoints = this.getBreakpoints(server, uri);
115 | breakpoints.push(new Breakpoint(uri, line, true));
116 | this.setBreakpoints(server, uri, breakpoints);
117 | }
118 |
119 | toggleBreakpoint(server: string, uri: string, line: number) {
120 | let breakpoints = this.getBreakpoints(server, uri);
121 | let breakpoint = _.find(breakpoints, (breakpoint: Breakpoint) => {
122 | return breakpoint.uri === uri && breakpoint.line === line;
123 | });
124 | if (breakpoint) {
125 | breakpoint.enabled = !breakpoint.enabled;
126 | this.setBreakpoints(server, uri, breakpoints);
127 | }
128 | }
129 |
130 | disableBreakpoint(server: string, uri: string, line: number) {
131 | let breakpoints: Array = this.getBreakpoints(server, uri);
132 | _.remove(breakpoints, (bp) => { return bp.line === line; });
133 | if (breakpoints.length > 0) {
134 | this.setBreakpoints(server, uri, breakpoints);
135 | } else {
136 | this.removeBreakpoints(server, uri);
137 | }
138 | }
139 |
140 | sendBreakpoints(requestId: any, breakpoints: Array) {
141 | let onOnly = _.filter(breakpoints, { enabled: true });
142 | return this.http.post(`/api/requests/${requestId}/breakpoints`, onOnly);
143 | }
144 |
145 | evalExpression(requestId: any, expression: string) {
146 | return this.http.post(`/api/requests/${requestId}/eval`, expression).map((resp: Response) => {
147 | return resp.text();
148 | });
149 | }
150 |
151 | valueExpression(requestId: any, expression: string) {
152 | return this.http.post(`/api/requests/${requestId}/value`, expression).map((resp: Response) => {
153 | return resp.json();
154 | });
155 | }
156 |
157 | invokeModule(serverId: string, uri: string) {
158 | return this.http.post(`/api/servers/${serverId}/invoke?uri=${uri}`, null);
159 | }
160 |
161 | private setBreakpoints(server: string, uri, breakpoints: Array) {
162 | let allBreakpoints = this.getAllBreakpoints(server);
163 | allBreakpoints.set(uri, breakpoints);
164 | this.saveBreakpoints(server, allBreakpoints);
165 | }
166 |
167 | private removeBreakpoints(server: string, uri) {
168 | let allBreakpoints = this.getAllBreakpoints(server);
169 | allBreakpoints.delete(uri);
170 | this.saveBreakpoints(server, allBreakpoints);
171 | }
172 |
173 |
174 | private saveBreakpoints(server: string, breakpoints: Map>) {
175 | let serializeme = {};
176 | breakpoints.forEach((value, key) => {
177 | serializeme[key] = value;
178 | });
179 | localStorage.setItem(`breakpoints-${server}`, JSON.stringify(serializeme));
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/ui/app/home/home.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/colors';
2 |
3 | $hover-color: unquote("rgb(#{$palette-debugger-50})");
4 |
5 | /deep/ .CodeMirror {
6 | height: 100%;
7 | width: 100%;
8 |
9 | .current-statement {
10 | background-color: $hover-color;
11 | }
12 | }
13 |
14 | .clickable {
15 | color: #666;
16 | width: 20px;
17 | height: 20px;
18 | margin: auto;
19 | display: inline-block;
20 | border-radius: 2px;
21 | cursor: default;
22 |
23 | /deep/ mdl-icon {
24 | font-size: 20px;
25 | }
26 | &:hover {
27 | background-color: rgba(0,0,0,0.05);
28 | color: black;
29 | }
30 | }
31 |
32 | .invoke-module {
33 | top: 6px;
34 | position: relative;
35 | }
36 |
37 | .fill-height {
38 | display: flex;
39 | flex: 1;
40 | flex-direction: column;
41 | }
42 |
43 | /deep/ .breakpoints {
44 | width: 1.5em;
45 |
46 | .section-body {
47 | padding: 10px;
48 | overflow-y: scroll;
49 | }
50 | }
51 |
52 | /deep/ .currentlines {
53 | width: 1.5em;
54 | }
55 |
56 | /deep/ .breakpoint-enabled {
57 | color: #822;
58 | font-size: 14pt;
59 | font-weight: bold;
60 | position: relative;
61 | }
62 |
63 | /deep/ .breakpoint-disabled {
64 | color: #666;
65 | font-size: 14pt;
66 | font-weight: bold;
67 | position: relative;
68 | }
69 |
70 | /deep/ .mdl-layout__content {
71 | height: 100%;
72 | }
73 |
74 | .breakpoints {
75 | ul {
76 | margin: 0;
77 | padding: 0px;
78 | list-style: none;
79 | cursor: default;
80 |
81 | .breakpoint-symbol {
82 | cursor: pointer;
83 | color: #666;
84 |
85 | &.enabled {
86 | color: #822;
87 | }
88 | }
89 |
90 | .fa-times {
91 | float: right;
92 |
93 | &:hover {
94 | color: red;
95 | }
96 | }
97 |
98 | li {
99 | padding: 5px;
100 | &:hover {
101 | background-color: $hover-color;
102 | }
103 | }
104 | }
105 | }
106 |
107 | .requests {
108 | .debug-status {
109 | display: inline;
110 | float: right;
111 |
112 | mdl-switch.mdl-switch.is-upgraded {
113 | padding-left: 28px;
114 | display: inline;
115 | margin-right: 20px;
116 | top: -5px;
117 | }
118 | }
119 | ul {
120 | margin: 0;
121 | padding: 0px;
122 | list-style: none;
123 |
124 | li {
125 | &:hover {
126 | background-color: $hover-color;
127 | }
128 | padding: 5px;
129 |
130 | cursor: pointer;
131 |
132 | .request-id {
133 | font-size: 60%;
134 | }
135 |
136 | .clickable {
137 | top: 6px;
138 | position: relative;
139 | }
140 | }
141 | }
142 |
143 | .section-body {
144 | padding: 10px;
145 | overflow-y: scroll;
146 | }
147 | }
148 |
149 | [gm-grid] {
150 | border-top: 1px solid #ccc;
151 | height: 100%;
152 | overflow: hidden;
153 | }
154 |
155 | .section-title {
156 | background-color: #f3f3f3;
157 | color: #333;
158 | padding: 10px;
159 | border-bottom: 1px solid #ccc;
160 | }
161 |
162 | /deep/ app-file-browser {
163 | flex: 1 auto;
164 | }
165 |
166 | .files {
167 | min-width: 250px;
168 |
169 | .section-body {
170 | display: flex;
171 | flex: 1;
172 | flex-direction: column;
173 | overflow-y: scroll;
174 | }
175 | }
176 |
177 |
178 | [gm-divider].debug-divider {
179 | display: block;
180 | height: initial;
181 | border-top: 1px solid #ccc;
182 | border-bottom: 1px solid #ccc;
183 | background-color: #f3f3f3;
184 | color: rgb(51,51,51);
185 | }
186 |
187 | .debug-area {
188 | min-height: 150px;
189 |
190 | .debugger {
191 | position: relative;
192 |
193 | .section-body {
194 | display: flex;
195 | flex: 1;
196 | flex-direction: column;
197 | overflow-y: scroll;
198 | }
199 |
200 | .disabled {
201 | position: absolute;
202 | top: 0px;
203 | bottom: 0px;
204 | left: 0px;
205 | right: 0px;
206 | background: rgba(255,255,255, 0.90);
207 | z-index: 10000;
208 |
209 | span {
210 | display: inline-block;
211 | width: 100%;
212 | font-weight: bold;
213 | margin: auto;
214 | text-align: center;
215 | position: absolute;
216 | top: 50%;
217 | left: 50%;
218 | transform: translate(-50%, -50%);
219 | }
220 | }
221 | }
222 |
223 | /deep/ mdl-icon {
224 | font-size: 16px;
225 | }
226 |
227 | .debugger {
228 | .section-title {
229 | padding: 5px;
230 | line-height: 8px;
231 |
232 | span {
233 | cursor: default;
234 | padding: 0px 2px;
235 |
236 | }
237 | }
238 | }
239 | }
240 |
241 | ul.frames {
242 | list-style: none;
243 | padding: 0px;
244 | margin: 0px;
245 |
246 | li {
247 | padding: 5px;
248 | cursor: default;
249 | &:hover {
250 | background-color: $hover-color;
251 | }
252 |
253 | .fa-long-arrow-right {
254 | visibility: hidden;
255 | &.visible {
256 | visibility: visible;
257 | }
258 | }
259 | }
260 | }
261 |
262 | .mdl-layout__header {
263 | min-height: 48px;
264 | }
265 |
266 | .mdl-layout__header-row {
267 | height: 48px;
268 | padding: 0 40px 0 10px;
269 |
270 | .mdl-switch {
271 | width: initial;
272 | }
273 |
274 | .selected-server {
275 | margin-right: 20px;
276 | }
277 | }
278 |
279 | .flex-70 {
280 | .section-body {
281 | display: flex;
282 | flex: 1;
283 | flex-direction: column;
284 | overflow-y: scroll;
285 | }
286 |
287 | app-codemirror {
288 | display: flex;
289 | flex: 1;
290 | }
291 | }
292 |
293 | .prompt {
294 | color: blue;
295 | margin-left: 5px;
296 | }
297 |
298 | .no-request {
299 | padding: 20px;
300 | color: #666;
301 | }
302 |
303 | .console-input {
304 | border: none;
305 | background: transparent;
306 | width: 60%;
307 |
308 | &:focus {
309 | outline: 0;
310 | }
311 |
312 | .prompt {
313 | color: #333;
314 | }
315 | }
316 |
317 | .console-output {
318 | color: #666;
319 |
320 | .prompt {
321 | color: #666;
322 | }
323 |
324 | .error {
325 | background-color: rgba(255, 0, 0, 0.5);
326 | padding: 5px;
327 | border-radius: 5px;
328 | color: black;
329 | width: 100%;
330 | display: inline-block;
331 |
332 | a {
333 | cursor: pointer;
334 | }
335 |
336 | pre {
337 | width: 100%;
338 | white-space: pre-wrap;
339 | }
340 | }
341 | }
342 |
343 | .console-buttons {
344 | float: right;
345 | }
346 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to MarkLogic Debugger
2 |
3 | MarkLogic Debuggers welcomes new contributors. This document will guide you
4 | through the process.
5 |
6 | - [Issues and Bugs](#found-an-issue)
7 | - [Feature Requests](#want-a-feature)
8 | - [Building from Source](#building-the-framework-from-source)
9 | - [Submission Guidelines](#submission-guidelines)
10 |
11 | ## Found an Issue?
12 | If you find a bug in the source code or a mistake in the documentation, you can help us by submitting an issue to our [GitHub Issue Tracker][issue tracker]. Even better you can submit a Pull Request
13 | with a fix for the issue you filed.
14 |
15 | ## Want a Feature?
16 | You can request a new feature by submitting an issue to our [GitHub Issue Tracker][issue tracker]. If you
17 | would like to implement a new feature then first create a new issue and discuss it with one of our
18 | project maintainers.
19 |
20 | ## Building the Debugger from Source
21 | Looking to build the code from source? Look no further.
22 |
23 | #### Prerequisites
24 | You need these to get started
25 |
26 | - Java 8 JDK
27 | - Gradle 3.1 or newer
28 | - Node JS 6.5 or newer
29 | - Typings `npm -g install typings`
30 | - A decent IDE. IntelliJ is nice.
31 |
32 | #### Building from the command line
33 | To build the war file simply run this command:
34 |
35 | ```bash
36 | cd /path/to/marklogic-debugger/
37 | gradle build
38 | ```
39 |
40 | #### Running the Debugger UI from source
41 | Make sure you have the prerequisites installed.
42 |
43 | You will need to open two terminal windows.
44 |
45 | **Terminal window 1** - This runs the webapp.
46 | ```bash
47 | cd /path/to/marklogic-debugger
48 | gradle bootrun
49 | ```
50 |
51 | **Terminal window 2** - This runs the UI
52 | ```
53 | cd /path/to/marklogic-debugger
54 | npm install
55 | npm start
56 | ```
57 |
58 | Now open your browser to [http://localhost:4200](http://localhost:4200) to use the debug version of the UI.
59 |
60 | ## Submission Guidelines
61 |
62 | ### Submitting an Issue
63 | Before you submit your issue search the archive, maybe your question was already answered.
64 |
65 | If your issue appears to be a bug, and hasn't been reported, open a new issue.
66 | Help us to maximize the effort we can spend fixing issues and adding new
67 | features, by not reporting duplicate issues. Please fill out the issue template so that your issue can be dealt with quickly.
68 |
69 | ### Submitting a Pull Request
70 |
71 | #### Fork marklogic-debugger
72 |
73 | Fork the project [on GitHub](https://github.com/paxtonhare/marklogic-debugger/fork) and clone
74 | your copy.
75 |
76 | ```sh
77 | $ git clone git@github.com:username/marklogic-debugger.git
78 | $ cd marklogic-debugger
79 | $ git remote add upstream git://github.com/paxtonhare/marklogic-debugger.git
80 | ```
81 |
82 | We ask that you open an issue in the [issue tracker][] and get agreement from
83 | at least one of the project maintainers before you start coding.
84 |
85 | Nothing is more frustrating than seeing your hard work go to waste because
86 | your vision does not align with that of a project maintainer.
87 |
88 | #### Create a branch for your changes
89 |
90 | Okay, so you have decided to fix something. Create a feature branch
91 | and start hacking. **Note** that we use git flow and thus our most recent changes live on the develop branch.
92 |
93 | ```sh
94 | $ git checkout -b my-feature-branch -t origin/develop
95 | ```
96 |
97 | #### Formatting code
98 |
99 | We use [.editorconfig][] to configure our editors for proper code formatting. If you don't
100 | use a tool that supports editorconfig be sure to configure your editor to use the settings
101 | equivalent to our .editorconfig file.
102 |
103 | #### Commit your changes
104 |
105 | Make sure git knows your name and email address:
106 |
107 | ```sh
108 | $ git config --global user.name "J. Random User"
109 | $ git config --global user.email "j.random.user@example.com"
110 | ```
111 |
112 | Writing good commit logs is important. A commit log should describe what
113 | changed and why. Follow these guidelines when writing one:
114 |
115 | 1. The first line should be 50 characters or less and contain a short
116 | description of the change including the Issue number prefixed by a hash (#).
117 | 2. Keep the second line blank.
118 | 3. Wrap all other lines at 72 columns.
119 |
120 | A good commit log looks like this:
121 |
122 | ```
123 | Fixing Issue #123: make the whatchamajigger work in MarkLogic 8
124 |
125 | Body of commit message is a few lines of text, explaining things
126 | in more detail, possibly giving some background about the issue
127 | being fixed, etc etc.
128 |
129 | The body of the commit message can be several paragraphs, and
130 | please do proper word-wrap and keep columns shorter than about
131 | 72 characters or so. That way `git log` will show things
132 | nicely even when it is indented.
133 | ```
134 |
135 | The header line should be meaningful; it is what other people see when they
136 | run `git shortlog` or `git log --oneline`.
137 |
138 | #### Rebase your repo
139 |
140 | Use `git rebase` (not `git merge`) to sync your work from time to time.
141 |
142 | ```sh
143 | $ git fetch upstream
144 | $ git rebase upstream/develop
145 | ```
146 |
147 |
148 | #### Test your code
149 |
150 | Make sure the JUnit tests pass.
151 |
152 | ```sh
153 | $ gradle test
154 | ```
155 |
156 | Make sure that all tests pass. Please, do not submit patches that fail.
157 |
158 | #### Push your changes
159 |
160 | ```sh
161 | $ git push origin my-feature-branch
162 | ```
163 |
164 | #### Agree to the contributor License
165 |
166 | Before we can merge your changes, you need to sign a [Contributor License Agreement](http://developer.marklogic.com/products/cla). You only need to do this once.
167 |
168 | #### Submit the pull request
169 |
170 | Go to https://github.com/username/marklogic-debugger and select your feature branch. Click
171 | the 'Pull Request' button and fill out the form.
172 |
173 | Pull requests are usually reviewed within a few days. If you get comments
174 | that need to be to addressed, apply your changes in a separate commit and push that to your
175 | feature branch. Post a comment in the pull request afterwards; GitHub does
176 | not send out notifications when you add commits to existing pull requests.
177 |
178 | That's it! Thank you for your contribution!
179 |
180 |
181 | #### After your pull request is merged
182 |
183 | After your pull request is merged, you can safely delete your branch and pull the changes
184 | from the main (upstream) repository:
185 |
186 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
187 |
188 | ```shell
189 | git push origin --delete my-feature-branch
190 | ```
191 |
192 | * Check out the develop branch:
193 |
194 | ```shell
195 | git checkout develop -f
196 | ```
197 |
198 | * Delete the local branch:
199 |
200 | ```shell
201 | git branch -D my-feature-branch
202 | ```
203 |
204 | * Update your develop with the latest upstream version:
205 |
206 | ```shell
207 | git pull --ff upstream develop
208 | ```
209 |
210 | [issue tracker]: https://github.com/paxtonhare/marklogic-debugger/issues
211 | [.editorconfig]: http://editorconfig.org/
212 |
--------------------------------------------------------------------------------
/src/main/java/com/marklogic/debugger/auth/ConnectionAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package com.marklogic.debugger.auth;
2 |
3 | import com.marklogic.debugger.LoginInfo;
4 | import org.springframework.security.authentication.AuthenticationServiceException;
5 | import org.springframework.security.core.Authentication;
6 | import org.springframework.security.core.AuthenticationException;
7 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
8 | import org.springframework.util.Assert;
9 |
10 | import javax.servlet.http.HttpServletRequest;
11 | import javax.servlet.http.HttpServletResponse;
12 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
13 |
14 | /**
15 | * Processes an authentication form submission. Called
16 | * {@code AuthenticationProcessingFilter} prior to Spring Security 3.0.
17 | *
18 | * Login forms must present two parameters to this filter: a username and password. The
19 | * default parameter names to use are contained in the static fields
20 | * {@link #SPRING_SECURITY_FORM_USERNAME_KEY} and
21 | * {@link #SPRING_SECURITY_FORM_PASSWORD_KEY}. The parameter names can also be changed by
22 | * setting the {@code usernameParameter} and {@code passwordParameter} properties.
23 | *
24 | * This filter by default responds to the URL {@code /login}.
25 | *
26 | * @author Ben Alex
27 | * @author Colin Sampaleanu
28 | * @author Luke Taylor
29 | * @since 3.0
30 | */
31 |
32 | public class ConnectionAuthenticationFilter extends
33 | AbstractAuthenticationProcessingFilter {
34 | // ~ Static fields/initializers
35 | // =====================================================================================
36 |
37 | public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
38 | public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
39 | public static final String SPRING_SECURITY_FORM_HOST_KEY = "hostname";
40 | public static final String SPRING_SECURITY_FORM_PORT_KEY = "port";
41 |
42 | private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
43 | private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
44 | private String hostnameParameter = SPRING_SECURITY_FORM_HOST_KEY;
45 | private String hostportParameter = SPRING_SECURITY_FORM_PORT_KEY;
46 | private boolean postOnly = true;
47 |
48 | // ~ Constructors
49 | // ===================================================================================================
50 |
51 | public ConnectionAuthenticationFilter() {
52 | super(new AntPathRequestMatcher("/api/user/login", "POST"));
53 | }
54 |
55 | // ~ Methods
56 | // ========================================================================================================
57 |
58 | public Authentication attemptAuthentication(HttpServletRequest request,
59 | HttpServletResponse response) throws AuthenticationException {
60 | if (postOnly && !request.getMethod().equals("POST")) {
61 | throw new AuthenticationServiceException(
62 | "Authentication method not supported: " + request.getMethod());
63 | }
64 |
65 | String username = obtainUsername(request);
66 | String password = obtainPassword(request);
67 | String hostname = obtainHostname(request);
68 | Integer port = obtainPort(request);
69 | LoginInfo loginInfo = new LoginInfo();
70 | loginInfo.hostname = hostname;
71 | loginInfo.port = port;
72 | loginInfo.username = username;
73 | loginInfo.password = password;
74 | request.getSession().setAttribute("loginInfo", loginInfo);
75 |
76 | if (username == null) {
77 | username = "";
78 | }
79 |
80 | if (password == null) {
81 | password = "";
82 | }
83 |
84 | if (hostname == null) {
85 | hostname = "";
86 | }
87 |
88 | if (port == null) {
89 | port = 8000;
90 | }
91 |
92 | username = username.trim();
93 |
94 | ConnectionAuthenticationToken authRequest = new ConnectionAuthenticationToken(
95 | username, password, hostname, port);
96 |
97 | // Allow subclasses to set the "details" property
98 | setDetails(request, authRequest);
99 |
100 | return this.getAuthenticationManager().authenticate(authRequest);
101 | }
102 |
103 | /**
104 | * Enables subclasses to override the composition of the password, such as by
105 | * including additional values and a separator.
106 | *
107 | * This might be used for example if a postcode/zipcode was required in addition to
108 | * the password. A delimiter such as a pipe (|) should be used to separate the
109 | * password and extended value(s). The AuthenticationDao will need to
110 | * generate the expected password in a corresponding manner.
111 | *
112 | *
113 | * @param request so that request attributes can be retrieved
114 | *
115 | * @return the password that will be presented in the Authentication
116 | * request token to the AuthenticationManager
117 | */
118 | protected String obtainPassword(HttpServletRequest request) {
119 | return request.getParameter(passwordParameter);
120 | }
121 |
122 | /**
123 | * Enables subclasses to override the composition of the username, such as by
124 | * including additional values and a separator.
125 | *
126 | * @param request so that request attributes can be retrieved
127 | *
128 | * @return the username that will be presented in the Authentication
129 | * request token to the AuthenticationManager
130 | */
131 | protected String obtainUsername(HttpServletRequest request) {
132 | return request.getParameter(usernameParameter);
133 | }
134 |
135 | protected String obtainHostname(HttpServletRequest request) {
136 | return request.getParameter(hostnameParameter);
137 | }
138 |
139 | protected Integer obtainPort(HttpServletRequest request) {
140 | return Integer.parseInt(request.getParameter(hostportParameter));
141 | }
142 |
143 | /**
144 | * Provided so that subclasses may configure what is put into the authentication
145 | * request's details property.
146 | *
147 | * @param request that an authentication request is being created for
148 | * @param authRequest the authentication request object that should have its details
149 | * set
150 | */
151 | protected void setDetails(HttpServletRequest request,
152 | ConnectionAuthenticationToken authRequest) {
153 | authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
154 | }
155 |
156 | /**
157 | * Sets the parameter name which will be used to obtain the username from the login
158 | * request.
159 | *
160 | * @param usernameParameter the parameter name. Defaults to "username".
161 | */
162 | public void setUsernameParameter(String usernameParameter) {
163 | Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
164 | this.usernameParameter = usernameParameter;
165 | }
166 |
167 | /**
168 | * Sets the parameter name which will be used to obtain the password from the login
169 | * request..
170 | *
171 | * @param passwordParameter the parameter name. Defaults to "password".
172 | */
173 | public void setPasswordParameter(String passwordParameter) {
174 | Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
175 | this.passwordParameter = passwordParameter;
176 | }
177 |
178 | public void setHostnameParameter(String hostnameParameter) {
179 | Assert.hasText(hostnameParameter, "Hostname parameter must not be empty or null");
180 | this.hostnameParameter = hostnameParameter;
181 | }
182 |
183 | /**
184 | * Defines whether only HTTP POST requests will be allowed by this filter. If set to
185 | * true, and an authentication request is received which is not a POST request, an
186 | * exception will be raised immediately and authentication will not be attempted. The
187 | * unsuccessfulAuthentication() method will be called as if handling a failed
188 | * authentication.
189 | *
190 | * Defaults to true but may be overridden by subclasses.
191 | */
192 | public void setPostOnly(boolean postOnly) {
193 | this.postOnly = postOnly;
194 | }
195 |
196 | public final String getUsernameParameter() {
197 | return usernameParameter;
198 | }
199 |
200 | public final String getPasswordParameter() {
201 | return passwordParameter;
202 | }
203 |
204 | public final String getHostnameParameter() {
205 | return hostnameParameter;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/ui/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | http://{{hostname}}:{{port}} ●
6 | {{selectedServer.name}}
7 |
8 |
9 |
10 |
11 | Show Welcome Message
12 | Report an Issue
13 | Sign Out
14 |
15 |
16 |
17 |
18 |