Merge "Enable retries for the /authn/validate endpoint if it fails to connect"
authorJohn Franey <john.franey@att.com>
Fri, 17 Jul 2020 19:40:31 +0000 (19:40 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 17 Jul 2020 19:40:31 +0000 (19:40 +0000)
21 files changed:
auth/auth-batch/src/test/java/org/onap/aaf/auth/batch/helpers/JU_BatchDataViewTest.java
auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/hl/Function.java
auth/auth-cass/src/main/java/org/onap/aaf/auth/dao/hl/Question.java
auth/auth-cmd/pom.xml
auth/auth-cmd/src/main/java/org/onap/aaf/auth/cmd/Cmd.java
auth/auth-core/pom.xml
auth/auth-core/src/main/java/org/onap/aaf/auth/org/Organization.java
auth/auth-core/src/main/java/org/onap/aaf/auth/rserv/CachingFileAccess.java
auth/auth-fs/pom.xml
auth/auth-fs/src/main/java/org/onap/aaf/auth/fs/AAF_FS.java
auth/auth-hello/pom.xml
auth/auth-hello/src/main/java/org/onap/aaf/auth/hello/API_Hello.java
auth/auth-locate/pom.xml
auth/auth-locate/src/main/java/org/onap/aaf/auth/locate/api/API_AAFAccess.java
auth/auth-locate/src/main/java/org/onap/aaf/auth/locate/facade/LocateFacadeImpl.java
cadi/client/src/main/java/org/onap/aaf/cadi/http/HClient.java
docs/conf.py
docs/sections/release-notes.rst
misc/pom.xml
misc/xgen/pom.xml
misc/xgen/src/main/java/org/onap/aaf/misc/xgen/Section.java

index 2ddd984..8ff2ec5 100644 (file)
@@ -4,6 +4,9 @@
  * ===========================================================================
  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
  * ===========================================================================
+ * Modification Copyright © 2020 IBM.
+ * ===========================================================================
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -21,7 +24,7 @@
 
 package org.onap.aaf.auth.batch.helpers;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import java.io.IOException;
@@ -71,57 +74,57 @@ public class JU_BatchDataViewTest {
     @Test
     public void testNs() {
         Result<NsDAO.Data> retVal = batchDataViewObj.ns(trans, "test");
-        assertTrue(retVal.status == 9);
+               assertEquals(9,retVal.status);
 
         NS n = new NS("test1", "test2", "test3", 1, 2);
         NS.data.put("test", n);
         retVal = batchDataViewObj.ns(trans, "test");
-        assertTrue(retVal.status == 0);
+               assertEquals(0,retVal.status);
     }
 
     @Test
     public void testRoleByName() {
         Result<RoleDAO.Data> retVal = batchDataViewObj.roleByName(trans,
                 "test");
-        assertTrue(retVal.status == 9);
+        assertEquals(9,retVal.status);
 
         Role n = new Role("test1");
         n.rdd = new RoleDAO.Data();
         Role.byName.put("test", n);
         retVal = batchDataViewObj.roleByName(trans, "test");
-        assertTrue(retVal.status == 0);
+        assertEquals(0,retVal.status);
 
         n.rdd = null;
         Role.byName.put("test", n);
         retVal = batchDataViewObj.roleByName(trans, "test");
-        assertTrue(retVal.status == 9);
+        assertEquals(9,retVal.status);
     }
     @Test
     public void testUrsByRole() {
         Result<List<UserRoleDAO.Data>> retVal = batchDataViewObj
                 .ursByRole(trans, "test");
-        assertTrue(retVal.status == 9);
+        assertEquals(9,retVal.status);
 
         Role n = new Role("test1");
         n.rdd = new RoleDAO.Data();
         UserRole ur = new UserRole("user", "role", "ns", "rname", new Date());
         (new UserRole.DataLoadVisitor()).visit(ur);
         retVal = batchDataViewObj.ursByRole(trans, "role");
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status,0);
 
     }
     @Test
     public void testUrsByUser() {
         Result<List<UserRoleDAO.Data>> retVal = batchDataViewObj
                 .ursByUser(trans, "test");
-        assertTrue(retVal.status == 9);
+        assertEquals(retVal.status,9);
 
         Role n = new Role("test1");
         n.rdd = new RoleDAO.Data();
         UserRole ur = new UserRole("user", "role", "ns", "rname", new Date());
         (new UserRole.DataLoadVisitor()).visit(ur);
         retVal = batchDataViewObj.ursByUser(trans, "user");
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status,0);
 
     }
     @Test
@@ -129,7 +132,7 @@ public class JU_BatchDataViewTest {
         FutureDAO.Data dataObj = new FutureDAO.Data();
         dataObj.id = new UUID(1000L, 1000L);
         Result<FutureDAO.Data> retVal = batchDataViewObj.delete(trans, dataObj);
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status,0);
 
     }
     @Test
@@ -138,7 +141,7 @@ public class JU_BatchDataViewTest {
         dataObj.id = new UUID(1000L, 1000L);
         Result<ApprovalDAO.Data> retVal = batchDataViewObj.delete(trans,
                 dataObj);
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status, 0);
 
     }
 
@@ -150,7 +153,7 @@ public class JU_BatchDataViewTest {
         dataObj.ticket = new UUID(1000L, 1000L);
         Result<ApprovalDAO.Data> retVal = batchDataViewObj.insert(trans,
                 dataObj);
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status, 0);
 
     }
     @Test
@@ -160,11 +163,11 @@ public class JU_BatchDataViewTest {
         dataObj.memo = "memo";
         dataObj.construct = ByteBuffer.allocate(1000);
         Result<FutureDAO.Data> retVal = batchDataViewObj.insert(trans, dataObj);
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status, 0);
 
         dataObj.target_key = "memo";
         retVal = batchDataViewObj.insert(trans, dataObj);
-        assertTrue(retVal.status == 0);
+        assertEquals(retVal.status, 0);
     }
     @Test
     public void testFlush() {
index e5cde35..761ebec 100644 (file)
@@ -759,7 +759,7 @@ public class Function {
             }
 
             for (CredDAO.Data cd : cdr.value) {
-                if (cd.expires.after(now)) {
+                if (cd.expires.after(now) || trans.org().isUserExpireExempt(cd.id, cd.expires)) {
                     return Result.ok();
                 }
             }
@@ -1440,7 +1440,7 @@ public class Function {
         List<UserRoleDAO.Data> list = rurdd.value;
         List<String> rv = new ArrayList<>(list.size()); // presize
         for (UserRoleDAO.Data urdd : rurdd.value) {
-            if (includeExpired || urdd.expires.after(now)) {
+            if (includeExpired || urdd.expires.after(now) || trans.org().isUserExpireExempt(urdd.user, urdd.expires)) {
                 rv.add(urdd.user);
             }
         }
index 39578f8..2e8e55f 100644 (file)
@@ -938,7 +938,7 @@ public class Question {
                     if (!cdd.id.equals(user)) {
                         trans.error().log("doesUserCredMatch DB call does not match for user: " + user);
                     }
-                    if (cdd.expires.after(now)) {
+                    if (cdd.expires.after(now) || trans.org().isUserExpireExempt(cdd.id, cdd.expires)) {
                         byte[] dbcred = cdd.cred.array();
 
                         try {
@@ -1273,7 +1273,7 @@ public class Question {
         if (rur.isOKhasData()) {
             Date now = new Date();
             for (UserRoleDAO.Data urdd : rur.value){
-                if (urdd.expires.after(now)) {
+                if (urdd.expires.after(now) || trans.org().isUserExpireExempt(urdd.user, urdd.expires)) {
                     return true;
                 }
             }
@@ -1285,7 +1285,7 @@ public class Question {
         Result<List<UserRoleDAO.Data>> rur = userRoleDAO().read(trans, user,ns+DOT_OWNER);
         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
             Date now = new Date();
-            if (urdd.expires.after(now)) {
+            if (urdd.expires.after(now) || trans.org().isUserExpireExempt(urdd.user, urdd.expires)) {
                 return true;
             }
         }};
@@ -1297,7 +1297,7 @@ public class Question {
         Date now = new Date();
         int count = 0;
         if (rur.isOKhasData()) {for (UserRoleDAO.Data urdd : rur.value){
-            if (urdd.expires.after(now)) {
+            if (urdd.expires.after(now) || trans.org().isUserExpireExempt(urdd.user, urdd.expires)) {
                 ++count;
             }
         }};
index 7133a5b..01ec4ec 100644 (file)
             <artifactId>jline</artifactId>
             <version>2.14.2</version>
         </dependency>
-
+        <dependency>           
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>
     </dependencies>
 
     <distributionManagement>
index 0ae4ce9..40616ab 100644 (file)
@@ -54,6 +54,7 @@ import aaf.v2_0.History;
 import aaf.v2_0.History.Item;
 import aaf.v2_0.Request;
 
+import org.owasp.encoder.Encode;
 
 public abstract class Cmd {
     // Sonar claims DateFormat is not thread safe.  Leave as Instance Variable.
@@ -272,7 +273,7 @@ public abstract class Cmd {
             sb.append(", ");
             sb.append(desc);
         }
-        pw().println(sb);
+        pw().println(Encode.forJava(sb.toString()));
     }
 
 
index 884ecbe..972b12c 100644 (file)
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
         </dependency>
+        <dependency>           
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>
     </dependencies>
     
     <build>
index 795231e..778eb29 100644 (file)
@@ -349,8 +349,8 @@ public interface Organization {
     public void setTestMode(boolean dryRun);
 
     /**
-     * Evaluates a user to determine if they are exempt from role expiration.
-     * Returns true if true, false is false. Default implementation is always false.
+     * Evaluates a user to determine if they are exempt from role and cred expiration.
+     * Returns true if true, false if false. Default implementation is always false.
      *
      * @param user
      * @param expires
index cdda50d..b342c42 100644 (file)
@@ -53,6 +53,7 @@ import org.onap.aaf.misc.env.EnvJAXB;
 import org.onap.aaf.misc.env.LogTarget;
 import org.onap.aaf.misc.env.Store;
 import org.onap.aaf.misc.env.Trans;
+import org.owasp.encoder.Encode;
 /*
  * CachingFileAccess
  *
@@ -429,9 +430,9 @@ public class CachingFileAccess<TRANS extends Trans> extends HttpCode<TRANS, Void
                     w.append(name);
                     w.append('/');
                 }
-                w.append(f.getName());
+                w.append(Encode.forJava(f.getName()));
                 w.append("\">");
-                w.append(f.getName());
+                w.append(Encode.forJava(f.getName()));
                 w.append("</a></li>\n");
             }
             w.append(F);
index 39cb03b..943c108 100644 (file)
             <groupId>org.onap.aaf.authz</groupId>
             <artifactId>aaf-cadi-core</artifactId>
         </dependency>
+         <dependency>  
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>   
+               <dependency>    
+            <groupId>org.owasp.esapi</groupId> 
+                       <artifactId>esapi</artifactId>  
+                       <version>2.0.1</version>        
+        </dependency>
     </dependencies>
 
     <build>
index 64d9353..fdedd6b 100644 (file)
@@ -45,7 +45,7 @@ import org.onap.aaf.cadi.config.Config;
 import org.onap.aaf.cadi.register.Registrant;
 import org.onap.aaf.cadi.register.RemoteRegistrant;
 
-
+import org.owasp.esapi.reference.DefaultHTTPUtilities;
 
 public class AAF_FS extends AbsService<AuthzEnv, AuthzTrans>  {
 
@@ -82,7 +82,8 @@ public class AAF_FS extends AbsService<AuthzEnv, AuthzTrans>  {
         @Override
         public void handle(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp) throws Exception {
             trans.info().printf("Redirecting %s to HTTP/S %s", req.getRemoteAddr(), req.getLocalAddr());
-            resp.sendRedirect(url);
+            DefaultHTTPUtilities util = new DefaultHTTPUtilities();            
+            util.sendRedirect(url);
         }
     };
 
index 11971e0..f9a420f 100644 (file)
             <groupId>org.onap.aaf.authz</groupId>
             <artifactId>aaf-cadi-aaf</artifactId>
         </dependency>
-
+               <dependency>            
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>
+               
     </dependencies>
 
     <build>
index 4ffb178..cdaa6a7 100644 (file)
@@ -35,6 +35,8 @@ import org.onap.aaf.auth.rserv.HttpMethods;
 import org.onap.aaf.misc.env.Env;
 import org.onap.aaf.misc.env.TimeTaken;
 
+import org.owasp.encoder.Encode;
+
 /**
  * API Apis
  * @author Jonathan
@@ -70,7 +72,7 @@ public class API_Hello {
                 String perm = pathParam(req, "perm");
                 if (perm!=null && perm.length()>0) {
                     os.print('(');
-                    os.print(req.getUserPrincipal().getName());
+                    os.print(Encode.forJava(req.getUserPrincipal().getName()));
                     TimeTaken tt = trans.start("Authorize perm", Env.REMOTE);
                     try {
                         if (req.isUserInRole(perm)) {
@@ -82,7 +84,7 @@ public class API_Hello {
                         tt.done();
                     }
                     os.print("Permission: ");
-                    os.print(perm);
+                    os.print(Encode.forJava(perm));
                     os.print(')');
                 }
                 os.println();
@@ -144,7 +146,7 @@ public class API_Hello {
                 }
                 sb.append("}");
                 ServletOutputStream os = resp.getOutputStream();
-                os.println(sb.toString());
+                os.println(Encode.forJava(sb.toString()));
                 trans.info().printf("Said 'RESTful Hello' to %s, Authentication type: %s",trans.getUserPrincipal().getName(),trans.getUserPrincipal().getClass().getSimpleName());
             }
         },APPLICATION_JSON);
@@ -164,7 +166,7 @@ public class API_Hello {
                 trans.info().printf("Content from %s: %s\n", pathParam(req, ":id"),content);
                 if (content.startsWith("{") && content.endsWith("}")) {
                     resp.setStatus(200 /* OK */);
-                    resp.getOutputStream().print(content);
+                    resp.getOutputStream().print(Encode.forJava(content));
                 } else {
                     resp.getOutputStream().write(NOT_JSON);
                     resp.setStatus(406);
index 2b6568b..3658598 100644 (file)
             <groupId>org.onap.aaf.authz</groupId>
             <artifactId>aaf-misc-rosetta</artifactId>
         </dependency>
+        <dependency>           
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>
+               <dependency>    
+            <groupId>org.owasp.esapi</groupId> 
+                       <artifactId>esapi</artifactId>  
+                       <version>2.0.1</version>        
+        </dependency>
+        
     </dependencies>
 
     <build>
index 36a987e..7b23c89 100644 (file)
@@ -53,6 +53,9 @@ import org.onap.aaf.cadi.client.Retryable;
 import org.onap.aaf.misc.env.APIException;
 import org.onap.aaf.misc.env.Env;
 import org.onap.aaf.misc.env.TimeTaken;
+import org.owasp.esapi.errors.AccessControlException;
+import org.owasp.esapi.reference.DefaultHTTPUtilities;
+import org.owasp.encoder.Encode;
 
 public class API_AAFAccess {
 //    private static String service, version, envContext;
@@ -104,7 +107,7 @@ public class API_AAFAccess {
                                         ServletOutputStream sos;
                                         try {
                                             sos = resp.getOutputStream();
-                                            sos.print(fp.value);
+                                            sos.print(Encode.forJava(fp.value));
                                         } catch (IOException e) {
                                             throw new CadiException(e);
                                         }
@@ -122,7 +125,7 @@ public class API_AAFAccess {
                         User u = (User)d.data.get(0);
                         resp.setStatus(u.code);
                         ServletOutputStream sos = resp.getOutputStream();
-                        sos.print(u.resp);
+                        sos.print(Encode.forJava(u.resp));
                     }
                 } finally {
                     tt.done();
@@ -256,7 +259,7 @@ public class API_AAFAccess {
         });
     }
 
-    private static void redirect(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp, LocateFacade context, Locator<URI> loc, String path) throws IOException {
+    private static void redirect(AuthzTrans trans, HttpServletRequest req, HttpServletResponse resp, LocateFacade context, Locator<URI> loc, String path) throws IOException, AccessControlException {
         try {
             if (loc.hasItems()) {
                 Item item = loc.best();
@@ -270,7 +273,9 @@ public class API_AAFAccess {
                     redirectURL.append(str);
                 }
                 trans.info().log("Redirect to",redirectURL);
-                resp.sendRedirect(redirectURL.toString());
+                DefaultHTTPUtilities util = new DefaultHTTPUtilities();                
+                util.sendRedirect(redirectURL.toString());                
+                //resp.sendRedirect(redirectURL.toString());
             } else {
                 context.error(trans, resp, Result.err(Result.ERR_NotFound,"No Locations found for redirection"));
             }
index 6710708..047663c 100644 (file)
@@ -59,6 +59,7 @@ import org.onap.aaf.misc.env.Env;
 import org.onap.aaf.misc.env.TimeTaken;
 import org.onap.aaf.misc.rosetta.env.RosettaDF;
 import org.onap.aaf.misc.rosetta.env.RosettaData;
+import org.owasp.encoder.Encode;
 
 import locate_local.v1_0.Api;
 
@@ -266,7 +267,7 @@ public abstract class LocateFacadeImpl<IN,OUT,ENDPOINTS,MGMT_ENDPOINTS,CONFIGURA
         TimeTaken tt = trans.start(API_EXAMPLE, Env.SUB);
         try {
             String content =Examples.print(apiDF.getEnv(), nameOrContentType, optional);
-            resp.getOutputStream().print(content);
+            resp.getOutputStream().print(Encode.forJava(content));
             setContentType(resp,content.contains("<?xml")?TYPE.XML:TYPE.JSON);
             return Result.ok();
         } catch (Exception e) {
@@ -311,7 +312,7 @@ public abstract class LocateFacadeImpl<IN,OUT,ENDPOINTS,MGMT_ENDPOINTS,CONFIGURA
                     }
                 }
             }
-            resp.getOutputStream().println(output);
+            resp.getOutputStream().println(Encode.forJava(output));
             setContentType(resp,epDF.getOutType());
             return Result.ok();
         } catch (Exception e) {
index c7b2605..898b99c 100644 (file)
@@ -47,7 +47,7 @@ import org.onap.aaf.misc.env.Data;
 import org.onap.aaf.misc.env.Data.TYPE;
 import org.onap.aaf.misc.env.util.Pool.Pooled;
 import org.onap.aaf.misc.rosetta.env.RosettaDF;
-
+import org.owasp.encoder.Encode;
 /**
  * Low Level Http Client Mechanism. Chances are, you want the high level "HRcli"
  * for Rosetta Object Translation
@@ -396,8 +396,10 @@ public class HClient implements EClient<HttpURLConnection> {
                     // reuse Buffers
                     Pooled<byte[]> pbuff = Rcli.buffPool.get();
                     try {
+                       String strTemp;
                         while ((read=is.read(pbuff.content))>=0) {
-                            os.write(pbuff.content,0,read);
+                               strTemp = new String(pbuff.content,0,read);                             
+                               os.write(Encode.forJava(strTemp).getBytes());
                         }
                     } finally {
                         pbuff.done();
@@ -412,8 +414,10 @@ public class HClient implements EClient<HttpURLConnection> {
                         errContent = new StringBuilder();
                         Pooled<byte[]> pbuff = Rcli.buffPool.get();
                         try {
+                               String strTemp; 
                             while ((read=is.read(pbuff.content))>=0) {
-                                os.write(pbuff.content,0,read);
+                               strTemp = new String(pbuff.content,0,read);                             
+                               os.write(Encode.forJava(strTemp).getBytes());
                             }
                         } finally {
                             pbuff.done();
index 8f40e8b..5371015 100644 (file)
@@ -12,4 +12,4 @@ intersphinx_mapping = {}
 html_last_updated_fmt = '%d-%b-%y %H:%M'
 
 def setup(app):
-    app.add_stylesheet("css/ribbon_onap.css")
+    app.add_stylesheet("css/ribbon.css")
index b7beed3..7981ed4 100644 (file)
@@ -6,6 +6,25 @@
 Release Notes
 =============
 
+Version: 2.1.23 (Frankfurt, 6.0.0)
+---------------------------------------------
+
+:Release Date: 2020-06-05
+
+**New Features**
+Certificate Management Protocol Version 2 (CMPv2) support was added to retrieve X.509 certificates from servers which supports CMPv2 over HTTP. SDNC as first ONAP component was integrated to enroll certificate from CMPv2 server to protect traffic between SDNC and Network Functions (xNFs).
+More details about CMPv2 support in ONAP can be found on a dedicated page.
+
+
+**Bug Fixes**
+       - `AAF-383 <https://jira.onap.org/browse/AAF-383>`_ AAF aaf-sms chart should use nodePortPrefix variable
+       - `AAF-783 <https://jira.onap.org/browse/AAF-783>`_ Consul container is outdated
+    - `AAF-784 <https://jira.onap.org/browse/AAF-784>`_ Vault container is outdated
+    - `AAF-1102 <https://jira.onap.org/browse/AAF-1102>`_ Pods still run as root
+
+**Known Issues - solve in Guilin**
+    - `AAF-1087 <https://jira.onap.org/browse/AAF-1087>`_ AAF init containers init with exit 0 even if failing
+
 Version: 2.1.15 (El Alto, 5.0.1)
 ---------------------------------------------
 
index 66851bc..61d4f5d 100644 (file)
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>           
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>
+        
     </dependencies>
 
     <modules>
index d24e851..d4183fb 100644 (file)
             <artifactId>aaf-misc-env</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>           
+                       <groupId>org.owasp.encoder</groupId>            
+                       <artifactId>encoder</artifactId>                
+                       <version>1.2.1</version>                
+               </dependency>        
     </dependencies>
     
     <!-- ============================================================== -->
index 9f1f2a3..0d41bd9 100644 (file)
@@ -28,6 +28,7 @@ import org.onap.aaf.misc.env.APIException;
 import org.onap.aaf.misc.env.Env;\r
 import org.onap.aaf.misc.env.Trans;\r
 import org.onap.aaf.misc.xgen.html.State;\r
+import org.owasp.encoder.Encode;\r
 \r
 public class Section<G extends XGen<G>> {\r
     protected int indent;\r
@@ -48,11 +49,11 @@ public class Section<G extends XGen<G>> {
     }\r
 \r
     public void forward(Writer w) throws IOException {\r
-        w.write(forward);\r
+       w.write(Encode.forJava(forward));\r
     }\r
     \r
     public void back(Writer w) throws IOException {\r
-        w.write(backward);\r
+       w.write(Encode.forJava(backward));\r
     }\r
     \r
     public String toString() {\r